diff options
Diffstat (limited to 'lib/Utils')
-rw-r--r-- | lib/Utils/src/Extensions/MemoryExtensions.cs | 21 | ||||
-rw-r--r-- | lib/Utils/src/IO/VnMemoryStream.cs | 38 | ||||
-rw-r--r-- | lib/Utils/src/Memory/Caching/ObjectRentalBase.cs | 70 | ||||
-rw-r--r-- | lib/Utils/src/Memory/Caching/ReusableStore.cs | 61 | ||||
-rw-r--r-- | lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs | 64 | ||||
-rw-r--r-- | lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs | 14 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtil.cs | 35 | ||||
-rw-r--r-- | lib/Utils/src/VnEncoding.cs | 97 | ||||
-rw-r--r-- | lib/Utils/tests/Async/AsyncAccessSerializerTests.cs | 27 | ||||
-rw-r--r-- | lib/Utils/tests/IO/VnMemoryStreamTests.cs | 99 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 2 |
11 files changed, 342 insertions, 186 deletions
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index 32bb3d4..6525db4 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -438,6 +438,27 @@ namespace VNLib.Utils.Extensions } /// <summary> + /// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="heap"></param> + /// <param name="initialData">The initial data to set the buffer to</param> + /// <returns>The initalized <see cref="MemoryHandle{T}"/> block</returns> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlyMemory<T> initialData) where T : unmanaged + { + //Aloc block + MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length); + + //Copy initial data + MemoryUtil.Copy(initialData, handle, 0); + + return handle; + } + + /// <summary> /// Copies data from the input buffer to the current handle and resizes the handle to the /// size of the buffer /// </summary> diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs index d97036d..eed4ca2 100644 --- a/lib/Utils/src/IO/VnMemoryStream.cs +++ b/lib/Utils/src/IO/VnMemoryStream.cs @@ -94,7 +94,7 @@ namespace VNLib.Utils.IO /// buffer of the specified size on the specified heap to avoid resizing. /// </summary> /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> - /// <param name="bufferSize">Number of bytes (length) of the stream if known</param> + /// <param name="bufferSize">The initial internal buffer size, does not effect the length/size of the stream, helps pre-alloc</param> /// <param name="zero">Zero memory allocations during buffer expansions</param> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> @@ -119,7 +119,22 @@ namespace VNLib.Utils.IO _length = data.Length; _position = 0; } - + + /// <summary> + /// Creates a new memory stream from the data provided + /// </summary> + /// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param> + /// <param name="data">Initial data</param> + public VnMemoryStream(IUnmangedHeap heap, ReadOnlyMemory<byte> data) + { + _ = heap ?? throw new ArgumentNullException(nameof(heap)); + //Alloc the internal buffer to match the data stream + _buffer = heap.AllocAndCopy(data); + //Set length + _length = data.Length; + _position = 0; + } + /// <summary> /// WARNING: Dangerous constructor, make sure read-only and owns hanlde are set accordingly /// </summary> @@ -302,6 +317,23 @@ namespace VNLib.Utils.IO return bytesToRead; } + ///<inheritdoc/> + public override unsafe int ReadByte() + { + if (LenToPosDiff == 0) + { + return -1; + } + + //get the value at the current position + byte* ptr = _buffer.GetOffset(_position); + + //Increment position + _position++; + + //Return value + return ptr[0]; + } /* * Async reading will always run synchronously in a memory stream, @@ -469,7 +501,7 @@ namespace VNLib.Utils.IO /// If the current stream is a readonly stream, creates a shallow copy for reading only. /// </summary> /// <returns>New stream shallow copy of the internal stream</returns> - /// <exception cref="InvalidOperationException"></exception> + /// <exception cref="NotSupportedException"></exception> public object Clone() => GetReadonlyShallowCopy(); /* diff --git a/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs b/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs index 305d93f..ca07885 100644 --- a/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs +++ b/lib/Utils/src/Memory/Caching/ObjectRentalBase.cs @@ -39,8 +39,9 @@ namespace VNLib.Utils.Memory.Caching public static ObjectRental<TNew> Create<TNew>(int quota = 0) where TNew : class, new() { static TNew constructor() => new(); - return new ObjectRental<TNew>(constructor, null, null, quota); + return Create(constructor, null, null, quota); } + /// <summary> /// Creates a new <see cref="ObjectRental{T}"/> store with generic rental and return callback handlers /// </summary> @@ -50,18 +51,17 @@ namespace VNLib.Utils.Memory.Caching public static ObjectRental<TNew> Create<TNew>(Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class, new() { static TNew constructor() => new(); - return new ObjectRental<TNew>(constructor, rentCb, returnCb, quota); + return Create(constructor, rentCb, returnCb, quota); } + /// <summary> /// Creates a new <see cref="ObjectRental{T}"/> store with a generic constructor function /// </summary> /// <param name="constructor">The function invoked to create a new instance when required</param> /// <param name="quota">The maximum number of elements that will be cached</param> /// <returns></returns> - public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, int quota = 0) where TNew: class - { - return new ObjectRental<TNew>(constructor, null, null, quota); - } + public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, int quota = 0) where TNew : class => Create(constructor, null, null, quota); + /// <summary> /// Creates a new <see cref="ObjectRental{T}"/> store with generic rental and return callback handlers /// </summary> @@ -69,10 +69,8 @@ namespace VNLib.Utils.Memory.Caching /// <param name="rentCb">Function responsible for preparing an instance to be rented</param> /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param> /// <param name="quota">The maximum number of elements that will be cached</param> - public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class - { - return new ObjectRental<TNew>(constructor, rentCb, returnCb, quota); - } + public static ObjectRental<TNew> Create<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb, int quota = 0) where TNew : class + => new(constructor, rentCb, returnCb, quota); /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{TNew}"/> store with generic rental and return callback handlers @@ -82,10 +80,9 @@ namespace VNLib.Utils.Memory.Caching /// <param name="rentCb">Function responsible for preparing an instance to be rented</param> /// <param name="returnCb">Function responsible for cleaning up an instance before reuse</param> /// <returns>The initialized store</returns> - public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class - { - return new ThreadLocalObjectStorage<TNew>(constructor, rentCb, returnCb); - } + public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor, Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class + => new (constructor, rentCb, returnCb); + /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store with generic rental and return callback handlers /// </summary> @@ -94,62 +91,75 @@ namespace VNLib.Utils.Memory.Caching public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Action<TNew>? rentCb, Action<TNew>? returnCb) where TNew : class, new() { static TNew constructor() => new(); - return new ThreadLocalObjectStorage<TNew>(constructor, rentCb, returnCb); + return CreateThreadLocal(constructor, rentCb, returnCb); } + /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store /// </summary> public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>() where TNew : class, new() { static TNew constructor() => new(); - return new ThreadLocalObjectStorage<TNew>(constructor, null, null); + return CreateThreadLocal(constructor, null, null); } + /// <summary> /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> store with a generic constructor function /// </summary> /// <param name="constructor">The function invoked to create a new instance when required</param> /// <returns></returns> - public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor) where TNew : class - { - return new ThreadLocalObjectStorage<TNew>(constructor, null, null); - } + public static ThreadLocalObjectStorage<TNew> CreateThreadLocal<TNew>(Func<TNew> constructor) where TNew : class => CreateThreadLocal(constructor, null, null); /// <summary> - /// Creates a new <see cref="ReusableStore{T}"/> instance with a parameterless constructor + /// Creates a new <see cref="ObjectRental{T}"/> instance with a parameterless constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <param name="quota">The maximum number of elements that will be cached</param> /// <returns></returns> - public static ReusableStore<T> CreateReusable<T>(int quota = 0) where T : class, IReusable, new() + public static ObjectRental<T> CreateReusable<T>(int quota = 0) where T : class, IReusable, new() { static T constructor() => new(); - return new(constructor, quota); + return CreateReusable(constructor, quota); } + /// <summary> - /// Creates a new <see cref="ReusableStore{T}"/> instance with the specified constructor + /// Creates a new <see cref="ObjectRental{T}"/> instance with the specified constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <param name="constructor">The constructor function invoked to create new instances of the <see cref="IReusable"/> type</param> /// <param name="quota">The maximum number of elements that will be cached</param> /// <returns></returns> - public static ReusableStore<T> CreateReusable<T>(Func<T> constructor, int quota = 0) where T : class, IReusable => new(constructor, quota); + public static ObjectRental<T> CreateReusable<T>(Func<T> constructor, int quota = 0) where T : class, IReusable + { + //Rent/return callbacks + static void Rent(T item) => item.Prepare(); + static void Return(T item) => item.Release(); + + return Create(constructor, Rent, Return, quota); + } /// <summary> - /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance with a parameterless constructor + /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> instance with a parameterless constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <returns></returns> - public static ThreadLocalReusableStore<T> CreateThreadLocalReusable<T>() where T : class, IReusable, new() + public static ThreadLocalObjectStorage<T> CreateThreadLocalReusable<T>() where T : class, IReusable, new() { static T constructor() => new(); - return new(constructor); + return CreateThreadLocalReusable(constructor); } + /// <summary> - /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance with the specified constructor + /// Creates a new <see cref="ThreadLocalObjectStorage{T}"/> instance with the specified constructor /// </summary> /// <typeparam name="T">The <see cref="IReusable"/> type</typeparam> /// <param name="constructor">The constructor function invoked to create new instances of the <see cref="IReusable"/> type</param> /// <returns></returns> - public static ThreadLocalReusableStore<T> CreateThreadLocalReusable<T>(Func<T> constructor) where T : class, IReusable => new(constructor); + public static ThreadLocalObjectStorage<T> CreateThreadLocalReusable<T>(Func<T> constructor) where T : class, IReusable + { + static void Rent(T item) => item.Prepare(); + static void Return(T item) => item.Release(); + return new ThreadLocalObjectStorage<T>(constructor, Rent, Return); + } } } diff --git a/lib/Utils/src/Memory/Caching/ReusableStore.cs b/lib/Utils/src/Memory/Caching/ReusableStore.cs deleted file mode 100644 index aacd012..0000000 --- a/lib/Utils/src/Memory/Caching/ReusableStore.cs +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: ReusableStore.cs -* -* ReusableStore.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Utils is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; - -namespace VNLib.Utils.Memory.Caching -{ - - /// <summary> - /// A reusable object store that extends <see cref="ObjectRental{T}"/>, that allows for objects to be reused heavily - /// </summary> - /// <typeparam name="T">A reusable object</typeparam> - public class ReusableStore<T> : ObjectRental<T> where T : class, IReusable - { - internal ReusableStore(Func<T> constructor, int quota) :base(constructor, null, null, quota) - {} - ///<inheritdoc/> - public override T Rent() - { - //Rent the object (or create it) - T rental = base.Rent(); - //Invoke prepare function - rental.Prepare(); - //return object - return rental; - } - ///<inheritdoc/> - public override void Return(T item) - { - /* - * Clean up the item by invoking the cleanup function, - * and only return the item for reuse if the caller allows - */ - if (item.Release()) - { - base.Return(item); - } - } - } -}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs b/lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs deleted file mode 100644 index 83cd4d6..0000000 --- a/lib/Utils/src/Memory/Caching/ThreadLocalReusableStore.cs +++ /dev/null @@ -1,64 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: ThreadLocalReusableStore.cs -* -* ThreadLocalReusableStore.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Utils is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; - -namespace VNLib.Utils.Memory.Caching -{ - /// <summary> - /// A reusable object store that extends <see cref="ThreadLocalObjectStorage{T}"/>, that allows for objects to be reused heavily - /// in a thread-local cache - /// </summary> - /// <typeparam name="T">A reusable object</typeparam> - public class ThreadLocalReusableStore<T> : ThreadLocalObjectStorage<T> where T: class, IReusable - { - /// <summary> - /// Creates a new <see cref="ThreadLocalReusableStore{T}"/> instance - /// </summary> - internal ThreadLocalReusableStore(Func<T> constructor):base(constructor, null, null) - { } - ///<inheritdoc/> - public override T Rent() - { - //Rent the object (or create it) - T rental = base.Rent(); - //Invoke prepare function - rental.Prepare(); - //return object - return rental; - } - ///<inheritdoc/> - public override void Return(T item) - { - /* - * Clean up the item by invoking the cleanup function, - * and only return the item for reuse if the caller allows - */ - if (item.Release()) - { - base.Return(item); - } - } - } -}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs index 41b08c1..820c819 100644 --- a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs +++ b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs @@ -34,6 +34,7 @@ namespace VNLib.Utils.Memory.Diagnostics public class TrackedHeapWrapper : VnDisposeable, IUnmangedHeap { private readonly IUnmangedHeap _heap; + private readonly bool _ownsHeap; private readonly object _statsLock; private readonly ConcurrentDictionary<IntPtr, ulong> _table; @@ -62,13 +63,15 @@ namespace VNLib.Utils.Memory.Diagnostics /// Creates a new diagnostics wrapper for the heap /// </summary> /// <param name="heap">The heap to gather statistics on</param> - public TrackedHeapWrapper(IUnmangedHeap heap) + /// <param name="ownsHeap">If true, the wrapper will dispose the heap when disposed</param> + public TrackedHeapWrapper(IUnmangedHeap heap, bool ownsHeap) { _statsLock = new(); _table = new(); _heap = heap; - //Default min block size to 0 + //Default min block size to max _minBlockSize = ulong.MaxValue; + _ownsHeap = ownsHeap; } /// <summary> @@ -124,8 +127,11 @@ namespace VNLib.Utils.Memory.Diagnostics ///<inheritdoc/> protected override void Free() { - Heap.Dispose(); - } + if(_ownsHeap) + { + _heap.Dispose(); + } + } ///<inheritdoc/> public bool Free(ref IntPtr block) diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index f671da0..dc69e76 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -68,6 +68,11 @@ namespace VNLib.Utils.Memory public const string SHARED_HEAP_RAW_FLAGS = "VNLIB_SHARED_HEAP_RAW_FLAGS"; /// <summary> + /// The environment variable name used to specify the shared heap type + /// </summary> + public const string SHARED_HEAP_GLOBAL_ZERO = "VNLIB_SHARED_HEAP_GLOBAL_ZERO"; + + /// <summary> /// Initial shared heap size (bytes) /// </summary> public const nuint SHARED_HEAP_INIT_SIZE = 20971520; @@ -102,10 +107,12 @@ namespace VNLib.Utils.Memory { //Get env for heap diag _ = ERRNO.TryParse(Environment.GetEnvironmentVariable(SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV), out ERRNO diagEnable); + _ = ERRNO.TryParse(Environment.GetEnvironmentVariable(SHARED_HEAP_GLOBAL_ZERO), out ERRNO globalZero); Trace.WriteIf(diagEnable, "Shared heap diagnostics enabled"); + Trace.WriteIf(globalZero, "Shared heap global zero enabled"); - Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable), LazyThreadSafetyMode.PublicationOnly); + Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable, globalZero), LazyThreadSafetyMode.PublicationOnly); //Cleanup the heap on process exit AppDomain.CurrentDomain.DomainUnload += DomainUnloaded; @@ -143,12 +150,13 @@ namespace VNLib.Utils.Memory /// Initializes a new <see cref="IUnmangedHeap"/> determined by compilation/runtime flags /// and operating system type for the current proccess. /// </summary> + /// <param name="globalZero">If true, sets the <see cref="HeapCreation.GlobalZero"/> flag</param> /// <returns>An <see cref="IUnmangedHeap"/> for the current process</returns> /// <exception cref="SystemException"></exception> /// <exception cref="DllNotFoundException"></exception> - public static IUnmangedHeap InitializeNewHeapForProcess() => InitHeapInternal(false, false); + public static IUnmangedHeap InitializeNewHeapForProcess(bool globalZero = false) => InitHeapInternal(false, false, globalZero); - private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats) + private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats, bool globalZero) { bool IsWindows = OperatingSystem.IsWindows(); @@ -167,6 +175,9 @@ namespace VNLib.Utils.Memory */ cFlags |= isShared ? HeapCreation.IsSharedHeap : HeapCreation.None; + //Set global zero flag if requested + cFlags |= globalZero ? HeapCreation.GlobalZero : HeapCreation.None; + IUnmangedHeap heap; ERRNO userFlags = 0; @@ -207,7 +218,7 @@ namespace VNLib.Utils.Memory } //Enable heap statistics - return enableStats ? new TrackedHeapWrapper(heap) : heap; + return enableStats ? new TrackedHeapWrapper(heap, true) : heap; } /// <summary> @@ -230,7 +241,7 @@ namespace VNLib.Utils.Memory { return; } - + uint byteSize = ByteCount<T>((uint)block.Length); fixed (void* ptr = &MemoryMarshal.GetReference(block)) @@ -305,6 +316,14 @@ namespace VNLib.Utils.Memory } /// <summary> + /// Zeroes a block of memory of the given unmanaged type + /// </summary> + /// <typeparam name="T">The unmanaged type to zero</typeparam> + /// <param name="block">A pointer to the block of memory to zero</param> + /// <param name="itemCount">The number of elements in the block to zero</param> + public static void InitializeBlock<T>(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount); + + /// <summary> /// Zeroes a block of memory pointing to the structure /// </summary> /// <typeparam name="T">The structure type</typeparam> @@ -498,7 +517,7 @@ namespace VNLib.Utils.Memory CheckBounds(dest, destOffset, count); //Check if 64bit - if(sizeof(nuint) == 8) + if(sizeof(void*) == 8) { //Get the number of bytes to copy nuint byteCount = ByteCount<T>(count); @@ -510,7 +529,7 @@ namespace VNLib.Utils.Memory T* src = (T*)srcHandle.Pointer + offset; //pin array - fixed (T* dst = dest) + fixed (T* dst = &MemoryMarshal.GetArrayDataReference(dest)) { //Offset dest ptr T* dstOffset = dst + destOffset; @@ -592,7 +611,7 @@ namespace VNLib.Utils.Memory { if (offset + count > handle.Length) { - throw new ArgumentOutOfRangeException("The offset or count is outside of the range of the block of memory"); + throw new ArgumentOutOfRangeException(nameof(offset), "Offset or count are beyond the range of the supplied memory handle"); } } diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index b8f18bd..e945135 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -28,6 +28,7 @@ using System.Text; using System.Buffers; using System.Text.Json; using System.Threading; +using System.Diagnostics; using System.Buffers.Text; using System.Threading.Tasks; using System.Runtime.InteropServices; @@ -37,7 +38,6 @@ using VNLib.Utils.IO; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; - namespace VNLib.Utils { /// <summary> @@ -778,7 +778,7 @@ namespace VNLib.Utils { return Convert.TryToBase64Chars(buffer, base64, out int charsWritten, options: options) ? charsWritten : ERRNO.E_FAIL; } - + /* * Calc base64 padding chars excluding the length mod 4 = 0 case @@ -983,6 +983,99 @@ namespace VNLib.Utils } /// <summary> + /// Attempts to base64url encode the binary buffer to it's base64url encoded representation + /// in place, aka does not allocate a temporary buffer. The buffer must be large enough to + /// encode the data, if not the operation will fail. The data in this span will be overwritten + /// to do the conversion + /// </summary> + /// <param name="rawData">The raw data buffer that will be used to encode data aswell as read it</param> + /// <param name="length">The length of the binary data to encode</param> + /// <param name="includePadding">A value specifying whether base64 padding should be encoded</param> + /// <returns>The base64url encoded string</returns> + /// <exception cref="ArgumentException"></exception> + public static string ToBase64UrlSafeStringInPlace(Span<byte> rawData, int length, bool includePadding) + { + //Encode in place + if (Base64.EncodeToUtf8InPlace(rawData, length, out int converted) != OperationStatus.Done) + { + throw new ArgumentException("The input buffer was not large enough to encode in-place", nameof(rawData)); + } + + //trim to converted size + Span<byte> base64 = rawData[..converted]; + + //Make url safe + Base64ToUrlSafeInPlace(base64); + + //Remove padding + if (!includePadding) + { + base64 = base64.TrimEnd((byte)0x3d); + } + + //Convert to string + return Encoding.UTF8.GetString(base64); + } + + /// <summary> + /// Converts binary data to it's base64url encoded representation and may allocate a temporary + /// heap buffer. + /// </summary> + /// <param name="rawData">The binary data to encode</param> + /// <param name="includePadding">A value that indicates if the base64 padding characters should be included</param> + /// <returns>The base64url encoded string</returns> + /// <exception cref="ArgumentException"></exception> + public static string ToBase64UrlSafeString(ReadOnlySpan<byte> rawData, bool includePadding) + { + int maxBufSize = Base64.GetMaxEncodedToUtf8Length(rawData.Length); + + if(maxBufSize > MAX_STACKALLOC) + { + //alloc buffer + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(maxBufSize); + + return ConvertToBase64UrlStringInternal(rawData, buffer.Span, includePadding); + } + else + { + //Stack alloc buffer + Span<byte> buffer = stackalloc byte[maxBufSize]; + + return ConvertToBase64UrlStringInternal(rawData, buffer, includePadding); + } + } + + private static string ConvertToBase64UrlStringInternal(ReadOnlySpan<byte> rawData, Span<byte> buffer, bool includePadding) + { + //Conver to base64 + OperationStatus status = Base64.EncodeToUtf8(rawData, buffer, out _, out int written, true); + + //Check for invalid states + Debug.Assert(status != OperationStatus.DestinationTooSmall, "Buffer allocation was too small for the conversion"); + Debug.Assert(status != OperationStatus.NeedMoreData, "Need more data status was returned but is not valid for an encoding operation"); + + //Should never occur, but just in case, this is an input error + if (status == OperationStatus.InvalidData) + { + throw new ArgumentException("Your input data contained values that could not be converted to base64", nameof(rawData)); + } + + Span<byte> base64 = buffer[..written]; + + //Make url safe + Base64ToUrlSafeInPlace(base64); + + //Remove padding + if (!includePadding) + { + base64 = base64.TrimEnd((byte)0x3d); + } + + //Convert to string + return Encoding.UTF8.GetString(base64); + } + + /// <summary> /// Encodes the binary input buffer to its base64url safe utf8 encoding, and writes the output /// to the supplied buffer. Be sure to call <see cref="Base64.GetMaxEncodedToUtf8Length(int)"/> /// to allocate the correct size buffer for encoding diff --git a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs index 7119d21..3c9bde7 100644 --- a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs +++ b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs @@ -2,12 +2,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Linq; using System.Threading; +using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Linq; namespace VNLib.Utils.Async.Tests { @@ -101,7 +101,7 @@ namespace VNLib.Utils.Async.Tests int maxCount = 64; - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release Task entry = serializer.WaitAsync(DEFAULT_KEY); @@ -119,7 +119,7 @@ namespace VNLib.Utils.Async.Tests serializer.Release(DEFAULT_KEY); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); } @@ -149,7 +149,7 @@ namespace VNLib.Utils.Async.Tests using CancellationTokenSource cts = new(500); - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release await serializer.WaitAsync(DEFAULT_KEY, cts.Token); @@ -159,7 +159,7 @@ namespace VNLib.Utils.Async.Tests serializer.Release(DEFAULT_KEY); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); @@ -175,18 +175,19 @@ namespace VNLib.Utils.Async.Tests //Alloc serailzer base on string IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal); - int maxCount = 128; + const int maxCount = 128; + const int itterations = 20; string test = ""; Stopwatch timer = new(); using CancellationTokenSource cts = new(500); - for (int i = 0; i < 10; i++) + for (int i = 0; i < itterations; i++) { test = ""; timer.Restart(); - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release await serializer.WaitAsync(DEFAULT_KEY, cts.Token); @@ -196,7 +197,7 @@ namespace VNLib.Utils.Async.Tests serializer.Release(DEFAULT_KEY); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); @@ -208,12 +209,12 @@ namespace VNLib.Utils.Async.Tests using SemaphoreSlim slim = new(1,1); - for (int i = 0; i < 10; i++) + for (int i = 0; i < itterations; i++) { test = ""; timer.Restart(); - Task[] asyncArr = new int[maxCount].Select(async p => + Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () => { //Take a lock then random delay, then release await slim.WaitAsync(cts.Token); @@ -222,7 +223,7 @@ namespace VNLib.Utils.Async.Tests test += "0"; slim.Release(); - }).ToArray(); + })).ToArray(); Task.WaitAll(asyncArr); diff --git a/lib/Utils/tests/IO/VnMemoryStreamTests.cs b/lib/Utils/tests/IO/VnMemoryStreamTests.cs new file mode 100644 index 0000000..3eb95ce --- /dev/null +++ b/lib/Utils/tests/IO/VnMemoryStreamTests.cs @@ -0,0 +1,99 @@ +using System; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.IO.Tests +{ + [TestClass()] + public class VnMemoryStreamTests + { + [TestMethod()] + public void VnMemoryStreamConstructorTest() + { + using (VnMemoryStream vms = new()) + { + Assert.IsTrue(vms.Length == 0); + Assert.IsTrue(vms.Position == 0); + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == true); + } + + //Test heap + IUnmangedHeap privateHeap = MemoryUtil.InitializeNewHeapForProcess(); + + using (VnMemoryStream vms = new(privateHeap, 1024, false)) + { + Assert.IsTrue(vms.Length == 0); + Assert.IsTrue(vms.Position == 0); + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == true); + } + + + //Create from mem handle + MemoryHandle<byte> handle = privateHeap.Alloc<byte>(byte.MaxValue); + + using (VnMemoryStream vms = VnMemoryStream.ConsumeHandle(handle, handle.GetIntLength(), false)) + { + Assert.IsTrue(vms.Length == byte.MaxValue); + Assert.IsTrue(vms.Position == 0); + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == true); + } + + //From existing data + ReadOnlySpan<byte> testSpan = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + using (VnMemoryStream vms = new (privateHeap, testSpan)) + { + Assert.IsTrue(vms.Length == testSpan.Length); + Assert.IsTrue(vms.Position == 0); + + //Check values copied + while (vms.Position < vms.Length) + { + byte test = testSpan[(int)vms.Position]; + Assert.IsTrue(vms.ReadByte() == test); + } + } + + ReadOnlyMemory<byte> testMemory = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + using (VnMemoryStream vms = new (privateHeap, testMemory)) + { + Assert.IsTrue(vms.Length == testMemory.Length); + Assert.IsTrue(vms.Position == 0); + + //Check values copied + while(vms.Position < vms.Length) + { + byte test = testMemory.Span[(int)vms.Position]; + Assert.IsTrue(vms.ReadByte() == test); + } + } + } + + [TestMethod()] + public void VnMemoryStreamReadonlyTest() + { + using VnMemoryStream vms = new(MemoryUtil.Shared, 0, false); + + Assert.IsTrue(vms.CanWrite == true); + + //Convert to readonly + _ = VnMemoryStream.CreateReadonly(vms); + + Assert.IsTrue(vms.CanSeek == true); + Assert.IsTrue(vms.CanRead == true); + Assert.IsTrue(vms.CanWrite == false); + + //Try to write + Assert.ThrowsException<NotSupportedException>(() => vms.WriteByte(0)); + + } + } +}
\ No newline at end of file diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 2166eea..64f94ff 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -388,7 +388,7 @@ namespace VNLib.Utils.Memory.Tests IUnmangedHeap heap = MemoryUtil.InitializeNewHeapForProcess(); //Init wrapper and dispose - using TrackedHeapWrapper wrapper = new(heap); + using TrackedHeapWrapper wrapper = new(heap, true); //Confirm 0 stats HeapStatistics preTest = wrapper.GetCurrentStats(); |