diff options
Diffstat (limited to 'lib/Utils')
19 files changed, 456 insertions, 378 deletions
diff --git a/lib/Utils/src/Async/AccessSerializer.cs b/lib/Utils/src/Async/AccessSerializer.cs deleted file mode 100644 index ce78f6c..0000000 --- a/lib/Utils/src/Async/AccessSerializer.cs +++ /dev/null @@ -1,297 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: AccessSerializer.cs -* -* AccessSerializer.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; -using System.Threading; -using System.Threading.Tasks; - -using VNLib.Utils.Resources; - -namespace VNLib.Utils.Async -{ - /// <summary> - /// Provides access arbitration to an exclusive resouce - /// </summary> - /// <typeparam name="TKey">The uinique identifier type for the resource</typeparam> - /// <typeparam name="TResource">The resource type</typeparam> - public sealed class AccessSerializer<TKey, TResource> where TResource : IExclusiveResource - { - private readonly SemaphoreSlim semaphore; - private readonly Func<TKey, TResource> Factory; - private readonly Action CompletedCb; - private int WaitingCount; - /// <summary> - /// Creates a new <see cref="AccessSerializer{K, T}"/> with the specified factory and completed callback - /// </summary> - /// <param name="factory">Factory function to genereate new <typeparamref name="TResource"/> objects from <typeparamref name="TKey"/> keys</param> - /// <param name="completedCb">Function to be invoked when the encapsulated objected is no longer in use</param> - /// <exception cref="ArgumentNullException"></exception> - public AccessSerializer(Func<TKey, TResource> factory, Action completedCb) - { - this.Factory = factory ?? throw new ArgumentNullException(nameof(factory)); - this.CompletedCb = completedCb; - //Setup semaphore for locking - this.semaphore = new SemaphoreSlim(1, 1); - this.WaitingCount = 0; - } - - /// <summary> - /// Attempts to obtain an exclusive lock on the object - /// </summary> - /// <param name="key"></param> - /// <param name="wait">Time to wait for lock</param> - /// <param name="exObj"></param> - /// <returns>true if lock was obtained within the timeout, false if the lock was not obtained</returns> - /// <exception cref="ObjectDisposedException"></exception> - /// <exception cref="ArgumentOutOfRangeException"></exception> - public bool TryWait(TKey key, TimeSpan wait, out ExclusiveResourceHandle<TResource> exObj) - { - //Increase waiting count while we wait - Interlocked.Increment(ref WaitingCount); - try - { - //Try to obtain the lock - if (semaphore.Wait(wait)) - { - TResource get() => Factory(key); - //Create new exclusive lock handle that will generate a new that calls release when freed - exObj = new(get, Release); - return true; - } - //Lock not taken - exObj = null; - return false; - } - finally - { - //Decrease the waiting count since we are no longer waiting - Interlocked.Decrement(ref WaitingCount); - } - } - /// <summary> - /// Waits for exclusive access to the resource. - /// </summary> - /// <param name="key"></param> - /// <returns>An <see cref="ExclusiveResourceHandle{T}"/> encapsulating the resource</returns> - public ExclusiveResourceHandle<TResource> Wait(TKey key) - { - try - { - //Increase waiting count while we wait - Interlocked.Increment(ref WaitingCount); - //Try to obtain the lock - semaphore.Wait(); - //Local function to generate the output value - TResource get() => Factory(key); - //Create new exclusive lock handle that will generate a new that calls release when freed - return new(get, Release); - } - finally - { - //Decrease the waiting count since we are no longer waiting - Interlocked.Decrement(ref WaitingCount); - } - } - /// <summary> - /// Asynchronously waits for exclusive access to the resource. - /// </summary> - /// <returns>An <see cref="ExclusiveResourceHandle{TResource}"/> encapsulating the resource</returns> - public async Task<ExclusiveResourceHandle<TResource>> WaitAsync(TKey key, CancellationToken cancellationToken = default) - { - try - { - //Increase waiting count while we wait - Interlocked.Increment(ref WaitingCount); - //Try to obtain the lock - await semaphore.WaitAsync(cancellationToken); - //Local function to generate the output value - TResource get() => Factory(key); - //Create new exclusive lock handle that will generate a new that calls release when freed - return new(get, Release); - } - finally - { - //Decrease the waiting count since we are no longer waiting - Interlocked.Decrement(ref WaitingCount); - } - } - /// <summary> - /// Releases an exclusive lock that is held on an object - /// </summary> - private void Release() - { - /* - * If objects are waiting for the current instance, then we will release - * the semaphore and exit, as we no longer have control over the context - */ - if (WaitingCount > 0) - { - this.semaphore.Release(); - } - else - { - //Do not release the sempahore, just dispose of the semaphore - this.semaphore.Dispose(); - //call the completed function - CompletedCb?.Invoke(); - } - } - } - - /// <summary> - /// Provides access arbitration to an <see cref="IExclusiveResource"/> - /// </summary> - /// <typeparam name="TKey">The uinique identifier type for the resource</typeparam> - /// <typeparam name="TArg">The type of the optional argument to be passed to the user-implemented factory function</typeparam> - /// <typeparam name="TResource">The resource type</typeparam> - public sealed class AccessSerializer<TKey, TArg, TResource> where TResource : IExclusiveResource - { - private readonly SemaphoreSlim semaphore; - private readonly Func<TKey, TArg, TResource> Factory; - private readonly Action CompletedCb; - private int WaitingCount; - /// <summary> - /// Creates a new <see cref="AccessSerializer{TKey, TArg, TResource}"/> with the specified factory and completed callback - /// </summary> - /// <param name="factory">Factory function to genereate new <typeparamref name="TResource"/> objects from <typeparamref name="TKey"/> keys</param> - /// <param name="completedCb">Function to be invoked when the encapsulated objected is no longer in use</param> - /// <exception cref="ArgumentNullException"></exception> - public AccessSerializer(Func<TKey, TArg, TResource> factory, Action completedCb) - { - this.Factory = factory ?? throw new ArgumentNullException(nameof(factory)); - this.CompletedCb = completedCb; - //Setup semaphore for locking - this.semaphore = new SemaphoreSlim(1, 1); - this.WaitingCount = 0; - } - - /// <summary> - /// Attempts to obtain an exclusive lock on the object - /// </summary> - /// <param name="key"></param> - /// <param name="arg">The key identifying the resource</param> - /// <param name="wait">Time to wait for lock</param> - /// <param name="exObj"></param> - /// <returns>true if lock was obtained within the timeout, false if the lock was not obtained</returns> - /// <exception cref="ObjectDisposedException"></exception> - /// <exception cref="ArgumentOutOfRangeException"></exception> - public bool TryWait(TKey key, TArg arg, TimeSpan wait, out ExclusiveResourceHandle<TResource> exObj) - { - //Increase waiting count while we wait - Interlocked.Increment(ref WaitingCount); - try - { - //Try to obtain the lock - if (semaphore.Wait(wait)) - { - TResource get() => Factory(key, arg); - //Create new exclusive lock handle that will generate a new that calls release when freed - exObj = new(get, Release); - return true; - } - //Lock not taken - exObj = null; - return false; - } - finally - { - //Decrease the waiting count since we are no longer waiting - Interlocked.Decrement(ref WaitingCount); - } - } - /// <summary> - /// Waits for exclusive access to the resource. - /// </summary> - /// <param name="key">The unique key that identifies the resource</param> - /// <param name="arg">The state argument to pass to the factory function</param> - /// <returns>An <see cref="ExclusiveResourceHandle{TResource}"/> encapsulating the resource</returns> - public ExclusiveResourceHandle<TResource> Wait(TKey key, TArg arg) - { - try - { - //Increase waiting count while we wait - Interlocked.Increment(ref WaitingCount); - //Try to obtain the lock - semaphore.Wait(); - //Local function to generate the output value - TResource get() => Factory(key, arg); - //Create new exclusive lock handle that will generate a new that calls release when freed - return new(get, Release); - } - finally - { - //Decrease the waiting count since we are no longer waiting - Interlocked.Decrement(ref WaitingCount); - } - } - /// <summary> - /// Asynchronously waits for exclusive access to the resource. - /// </summary> - /// <param name="key"></param> - /// <param name="arg">The state argument to pass to the factory function</param> - /// <param name="cancellationToken"></param> - /// <returns>An <see cref="ExclusiveResourceHandle{TResource}"/> encapsulating the resource</returns> - public async Task<ExclusiveResourceHandle<TResource>> WaitAsync(TKey key, TArg arg, CancellationToken cancellationToken = default) - { - try - { - //Increase waiting count while we wait - Interlocked.Increment(ref WaitingCount); - //Try to obtain the lock - await semaphore.WaitAsync(cancellationToken); - //Local function to generate the output value - TResource get() => Factory(key, arg); - //Create new exclusive lock handle that will generate a new that calls release when freed - return new(get, Release); - } - finally - { - //Decrease the waiting count since we are no longer waiting - Interlocked.Decrement(ref WaitingCount); - } - } - - /// <summary> - /// Releases an exclusive lock that is held on an object - /// </summary> - private void Release() - { - /* - * If objects are waiting for the current instance, then we will release - * the semaphore and exit, as we no longer have control over the context - */ - if (WaitingCount > 0) - { - this.semaphore.Release(); - } - else - { - //Do not release the sempahore, just dispose of the semaphore - this.semaphore.Dispose(); - //call the completed function - CompletedCb?.Invoke(); - } - } - } -}
\ No newline at end of file diff --git a/lib/Utils/src/Async/IAsyncAccessSerializer.cs b/lib/Utils/src/Async/IAsyncAccessSerializer.cs new file mode 100644 index 0000000..5dce3cd --- /dev/null +++ b/lib/Utils/src/Async/IAsyncAccessSerializer.cs @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: IAsyncAccessSerializer.cs +* +* IAsyncAccessSerializer.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.Threading; +using System.Threading.Tasks; + +namespace VNLib.Utils.Async +{ + /// <summary> + /// A mutual exclusion primitive that provides asynchronous waites for serialized + /// access to a resource based on a moniker. Similar to the <see cref="Monitor"/> + /// class. + /// </summary> + /// <typeparam name="TMoniker">The moniker type, the uniuqe token identifying the wait</typeparam> + public interface IAsyncAccessSerializer<TMoniker> + { + /// <summary> + /// Provides waiting for exclusve access identified + /// by the supplied moniker + /// </summary> + /// <param name="moniker">The moniker used to identify the wait</param> + /// <param name="cancellation">A token to cancel the async wait operation</param> + /// <returns>A task that completes when the wait identified by the moniker is released</returns> + Task WaitAsync(TMoniker moniker, CancellationToken cancellation = default); + + /// <summary> + /// Completes the exclusive access identified by the moniker + /// </summary> + /// <param name="moniker">The moniker used to identify the wait to release</param> + void Release(TMoniker moniker); + } +}
\ No newline at end of file diff --git a/lib/Utils/src/Extensions/CollectionExtensions.cs b/lib/Utils/src/Extensions/CollectionExtensions.cs index 7636cd3..b3a5d2a 100644 --- a/lib/Utils/src/Extensions/CollectionExtensions.cs +++ b/lib/Utils/src/Extensions/CollectionExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -25,6 +25,7 @@ using System; using System.Collections.Generic; + namespace VNLib.Utils.Extensions { /// <summary> @@ -63,6 +64,10 @@ namespace VNLib.Utils.Extensions /// <param name="value">The value to serialze</param> public static void SetValueType<TKey, TValue>(this IIndexable<TKey, string> lookup, TKey key, TValue value) where TValue : unmanaged where TKey : notnull { + if (lookup is null) + { + throw new ArgumentNullException(nameof(lookup)); + } //encode string from value type and store in lookup lookup[key] = VnEncoding.ToBase32String(value); } @@ -76,6 +81,16 @@ namespace VNLib.Utils.Extensions /// <exception cref="AggregateException"></exception> public static void TryForeach<T>(this IEnumerable<T> list, Action<T> handler) { + if (list is null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (handler is null) + { + throw new ArgumentNullException(nameof(handler)); + } + List<Exception>? exceptionList = null; foreach(T item in list) { diff --git a/lib/Utils/src/Extensions/SerializerHandle.cs b/lib/Utils/src/Extensions/SerializerHandle.cs new file mode 100644 index 0000000..3d410e2 --- /dev/null +++ b/lib/Utils/src/Extensions/SerializerHandle.cs @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: SerializerHandle.cs +* +* SerializerHandle.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; + + +using VNLib.Utils.Async; + +namespace VNLib.Utils.Extensions +{ + /// <summary> + /// Holds the exlcusive lock to the the <see cref="IAsyncAccessSerializer{TMoniker}"/> + /// and releases the lock when the handle is disposed + /// </summary> + /// <typeparam name="TMoniker">The moniker type</typeparam> + public readonly struct SerializerHandle<TMoniker> : IDisposable, IEquatable<SerializerHandle<TMoniker>> + { + private readonly IAsyncAccessSerializer<TMoniker> _serializer; + private readonly TMoniker _moniker; + + /// <summary> + /// Inializes a new <see cref="SerializerHandle{TMoniker}"/> that is holding + /// a lock to the given <see cref="IAsyncAccessSerializer{TMoniker}"/> by the + /// given <paramref name="moniker"/> + /// </summary> + /// <param name="moniker">The monike that referrences the entered lock</param> + /// <param name="serializer">The serialzer this handle will release the lock on when disposed</param> + public SerializerHandle(TMoniker moniker, IAsyncAccessSerializer<TMoniker> serializer) + { + _moniker = moniker; + _serializer = serializer; + } + + /// <summary> + /// Releases the exclusive lock on the moinker back to the serializer; + /// </summary> + public readonly void Dispose() => _serializer.Release(_moniker); + + ///<inheritdoc/> + public override bool Equals(object? obj) => obj is SerializerHandle<TMoniker> sh && Equals(sh); + + ///<inheritdoc/> + public override int GetHashCode() => _moniker?.GetHashCode() ?? 0; + + public static bool operator ==(SerializerHandle<TMoniker> left, SerializerHandle<TMoniker> right) => left.Equals(right); + + public static bool operator !=(SerializerHandle<TMoniker> left, SerializerHandle<TMoniker> right) => !(left == right); + + ///<inheritdoc/> + public bool Equals(SerializerHandle<TMoniker> other) + { + return (_moniker == null && other._moniker == null) || _moniker != null && _moniker.Equals(other._moniker); + } + } +} diff --git a/lib/Utils/src/Extensions/ThreadingExtensions.cs b/lib/Utils/src/Extensions/ThreadingExtensions.cs index cc9fab9..5180a11 100644 --- a/lib/Utils/src/Extensions/ThreadingExtensions.cs +++ b/lib/Utils/src/Extensions/ThreadingExtensions.cs @@ -25,6 +25,8 @@ using System; using System.Threading; using System.Threading.Tasks; + +using VNLib.Utils.Async; using VNLib.Utils.Resources; namespace VNLib.Utils.Extensions @@ -48,7 +50,48 @@ namespace VNLib.Utils.Extensions safeCallback(rh.Resource); } } - + + /// <summary> + /// Waits for exlcusive access to the resource identified by the given moniker + /// and returns a handle that will release the lock when disposed. + /// </summary> + /// <typeparam name="TMoniker"></typeparam> + /// <param name="serialzer"></param> + /// <param name="moniker">The moniker used to identify the lock</param> + /// <param name="cancellation">A token to cancel the wait operation</param> + /// <returns>A task that resolves a handle that holds the lock information and releases the lock when disposed</returns> + public static Task<SerializerHandle<TMoniker>> GetHandleAsync<TMoniker>( + this IAsyncAccessSerializer<TMoniker> serialzer, + TMoniker moniker, + CancellationToken cancellation = default + ) + { + //Wait async get handle + static async Task<SerializerHandle<TMoniker>> AwaitHandle(Task wait, IAsyncAccessSerializer<TMoniker> serialzer, TMoniker moniker) + { + await wait.ConfigureAwait(false); + return new SerializerHandle<TMoniker>(moniker, serialzer); + } + + //Enter the lock async + Task wait = serialzer.WaitAsync(moniker, cancellation); + + if (wait.IsCompleted) + { + //Allow throwing the exception if cancel or error + +#pragma warning disable CA1849 // Call async methods when in an async method + wait.GetAwaiter().GetResult(); +#pragma warning restore CA1849 // Call async methods when in an async method + + //return the new handle + return Task.FromResult(new SerializerHandle<TMoniker>(moniker, serialzer)); + } + + //Wait async + return AwaitHandle(wait, serialzer, moniker); + } + /// <summary> /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/> while observing a <see cref="CancellationToken"/> /// and getting a releaser handle diff --git a/lib/Utils/src/IO/IBufferReader.cs b/lib/Utils/src/IO/IBufferReader.cs new file mode 100644 index 0000000..7162a9b --- /dev/null +++ b/lib/Utils/src/IO/IBufferReader.cs @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: IBufferReader.cs +* +* IBufferReader.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.IO +{ + /// <summary> + /// A simple interface that provides the opposite of a buffer writer, + /// that is, allow reading segments from the internal buffer + /// and advancing the read position + /// </summary> + /// <typeparam name="T">The buffer data type</typeparam> + public interface IBufferReader<T> + { + /// <summary> + /// Advances the reader by the number of elements read + /// </summary> + /// <param name="count">The number of elements read</param> + void Advance(int count); + + /// <summary> + /// Gets the current data segment to read + /// </summary> + /// <returns>The current data segment to read</returns> + ReadOnlySpan<T> GetWindow(); + } +} diff --git a/lib/Utils/src/Memory/Caching/LRUCache.cs b/lib/Utils/src/Memory/Caching/LRUCache.cs index 6cbb425..30608af 100644 --- a/lib/Utils/src/Memory/Caching/LRUCache.cs +++ b/lib/Utils/src/Memory/Caching/LRUCache.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -65,20 +65,23 @@ namespace VNLib.Utils.Memory.Caching //Get the oldest node from the list to reuse its instance and remove the old value LinkedListNode<KeyValuePair<TKey, TValue>> oldNode = List.First!; //not null because count is at max capacity so an item must be at the end of the list - //Store old node value field + //Store old node value field on the stack ref KeyValuePair<TKey, TValue> oldRecord = ref oldNode.ValueRef; + //Remove from lookup LookupTable.Remove(oldRecord.Key); //Remove the node List.RemoveFirst(); + + //Invoke evicted method + Evicted(ref oldRecord); + //Reuse the old ll node oldNode.Value = item; //add lookup with new key LookupTable.Add(item.Key, oldNode); //Add to end of list List.AddLast(oldNode); - //Invoke evicted method - Evicted(ref oldRecord); } else { @@ -112,6 +115,7 @@ namespace VNLib.Utils.Memory.Caching //Record does not exist return false; } + /// <summary> /// Invoked when a record is evicted from the cache /// </summary> diff --git a/lib/Utils/src/Memory/Caching/ObjectRental.cs b/lib/Utils/src/Memory/Caching/ObjectRental.cs index 22aca95..235b7cd 100644 --- a/lib/Utils/src/Memory/Caching/ObjectRental.cs +++ b/lib/Utils/src/Memory/Caching/ObjectRental.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -23,13 +23,9 @@ */ using System; -using System.Threading; using System.Diagnostics; -using System.Collections; using System.Collections.Generic; -using VNLib.Utils.Extensions; - namespace VNLib.Utils.Memory.Caching { //TODO: implement lock-free object tracking @@ -39,14 +35,14 @@ namespace VNLib.Utils.Memory.Caching /// and its members is thread-safe /// </summary> /// <typeparam name="T">The data type to reuse</typeparam> - public class ObjectRental<T> : ObjectRental, IObjectRental<T>, ICacheHolder, IEnumerable<T> where T: class + public class ObjectRental<T> : ObjectRental, IObjectRental<T>, ICacheHolder where T: class { /// <summary> /// The initial data-structure capacity if quota is not defined /// </summary> public const int INITIAL_STRUCTURE_SIZE = 50; - protected readonly SemaphoreSlim StorageLock; + protected readonly object StorageLock; protected readonly Stack<T> Storage; protected readonly HashSet<T> ContainsStore; @@ -74,7 +70,7 @@ namespace VNLib.Utils.Memory.Caching //Hashtable for quick lookups ContainsStore = new(Math.Max(quota, INITIAL_STRUCTURE_SIZE)); //Semaphore slim to provide exclusive access - StorageLock = new SemaphoreSlim(1, 1); + StorageLock = new (); //Store quota, if quota is -1, set to int-max to "disable quota" QuotaLimit = quota == 0 ? int.MaxValue : quota; //Determine if the type is disposeable and store a local value @@ -102,17 +98,19 @@ namespace VNLib.Utils.Memory.Caching Check(); //See if we have an available object, if not return a new one by invoking the constructor function T? rental = default; - //Get lock - using (SemSlimReleaser releader = StorageLock.GetReleaser()) + + //Enter Lock + lock (StorageLock) { //See if the store contains an item ready to use - if(Storage.TryPop(out T? item)) + if (Storage.TryPop(out T? item)) { rental = item; //Remove the item from the hash table ContainsStore.Remove(item); } } + //If no object was removed from the store, create a new one rental ??= Constructor(); //If rental cb is defined, invoke it @@ -124,12 +122,17 @@ namespace VNLib.Utils.Memory.Caching /// <exception cref="ObjectDisposedException"></exception> public virtual void Return(T item) { + _ = item ?? throw new ArgumentNullException(nameof(item)); + Check(); + //Invoke return callback if set ReturnAction?.Invoke(item); + //Keeps track to know if the element was added or need to be cleaned up bool wasAdded = false; - using (SemSlimReleaser releader = StorageLock.GetReleaser()) + + lock (StorageLock) { //Check quota limit if (Storage.Count < QuotaLimit) @@ -144,6 +147,7 @@ namespace VNLib.Utils.Memory.Caching wasAdded = true; } } + if (!wasAdded && IsDisposableType) { //If the element was not added and is disposeable, we can dispose the element @@ -162,42 +166,85 @@ namespace VNLib.Utils.Memory.Caching public virtual void CacheClear() { Check(); + //If the type is disposeable, cleaning can be a long process, so defer to hard clear if (IsDisposableType) { return; } - //take the semaphore - using SemSlimReleaser releader = StorageLock.GetReleaser(); - //Clear stores - ContainsStore.Clear(); - Storage.Clear(); + + lock(StorageLock) + { + //Clear stores + ContainsStore.Clear(); + Storage.Clear(); + } } + /// <summary> + /// Gets all the elements in the store as a "snapshot" + /// while holding the lock + /// </summary> + /// <returns></returns> + protected T[] GetElementsWithLock() + { + T[] result; + + lock (StorageLock) + { + //Enumerate all items to the array + result = Storage.ToArray(); + } + + return result; + } + + /// <inheritdoc/> /// <exception cref="ObjectDisposedException"></exception> public virtual void CacheHardClear() { Check(); - //take the semaphore - using SemSlimReleaser releader = StorageLock.GetReleaser(); - //If the type is disposable, dispose all elements before clearing storage + + /* + * If the type is disposable, we need to collect all the stored items + * and dispose them individually. We need to spend as little time in + * the lock as possbile (busywaiting...) so get the array and exit + * the lock after clearing. Then we can dispose the elements. + * + * If the type is not disposable, we don't need to get the items + * and we can just call CacheClear() + */ + if (IsDisposableType) { + T[] result; + + //Enter Lock + lock (StorageLock) + { + //Enumerate all items to the array + result = Storage.ToArray(); + + //Clear stores + ContainsStore.Clear(); + Storage.Clear(); + } + //Dispose all elements - foreach (T element in Storage.ToArray()) + foreach (T element in result) { (element as IDisposable)!.Dispose(); } } - //Clear the storeage - Storage.Clear(); - ContainsStore.Clear(); + else + { + CacheClear(); + } } ///<inheritdoc/> protected override void Free() { - StorageLock.Dispose(); //If the element type is disposable, dispose all elements on a hard clear if (IsDisposableType) { @@ -209,28 +256,11 @@ namespace VNLib.Utils.Memory.Caching } } - ///<inheritdoc/> - public IEnumerator<T> GetEnumerator() - { - Check(); - //Enter the semaphore - using SemSlimReleaser releader = StorageLock.GetReleaser(); - foreach (T item in Storage) - { - yield return item; - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - Check(); - //Enter the semaphore - using SemSlimReleaser releader = StorageLock.GetReleaser(); - foreach (T item in Storage) - { - yield return item; - } - } + /// <summary> + /// Gets all the elements in the store currently. + /// </summary> + /// <returns>The current elements in storage as a "snapshot"</returns> + public virtual T[] GetItems() => GetElementsWithLock(); } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs b/lib/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs index 511af24..a2e5ef6 100644 --- a/lib/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs +++ b/lib/Utils/src/Memory/Caching/ThreadLocalObjectStorage.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -23,6 +23,7 @@ */ using System; +using System.Linq; using System.Threading; namespace VNLib.Utils.Memory.Caching @@ -65,7 +66,13 @@ namespace VNLib.Utils.Memory.Caching //Invoke the rent action base.ReturnAction?.Invoke(item); } - + + ///<inheritdoc/> + public override T[] GetItems() + { + return Store.Values.ToArray(); + } + ///<inheritdoc/> protected override void Free() { diff --git a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs index c850b14..39c9594 100644 --- a/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs +++ b/lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs @@ -39,7 +39,7 @@ namespace VNLib.Utils.Memory private int _position; /// <summary> - /// Initializes a new <see cref="FordwardOnlyMemoryReader{T}"/> + /// Initializes a new <see cref="ForwardOnlyMemoryReader{T}"/> /// of the specified type using the specified internal buffer /// </summary> /// <param name="buffer">The buffer to read from</param> diff --git a/lib/Utils/src/Memory/ForwardOnlyReader.cs b/lib/Utils/src/Memory/ForwardOnlyReader.cs index aa268c4..9829df3 100644 --- a/lib/Utils/src/Memory/ForwardOnlyReader.cs +++ b/lib/Utils/src/Memory/ForwardOnlyReader.cs @@ -39,7 +39,7 @@ namespace VNLib.Utils.Memory private int _position; /// <summary> - /// Initializes a new <see cref="FordwardOnlyReader{T}"/> + /// Initializes a new <see cref="ForwardOnlyReader{T}"/> /// of the specified type using the specified internal buffer /// </summary> /// <param name="buffer">The buffer to read from</param> @@ -51,6 +51,20 @@ namespace VNLib.Utils.Memory } /// <summary> + /// Initializes a new <see cref="ForwardOnlyReader{T}"/> + /// of the specified type using the specified internal buffer + /// begining at the specified offset + /// </summary> + /// <param name="buffer">The buffer to read from</param> + /// <param name="offset">The offset within the supplied buffer to begin the reader at</param> + public ForwardOnlyReader(in ReadOnlySpan<T> buffer, int offset) + { + _segment = buffer[offset..]; + _size = _segment.Length; + _position = 0; + } + + /// <summary> /// The remaining data window /// </summary> public readonly ReadOnlySpan <T> Window => _segment[_position..]; diff --git a/lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs b/lib/Utils/src/Memory/ForwardOnlyWriter.cs index 0ea507e..efd7f2b 100644 --- a/lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs +++ b/lib/Utils/src/Memory/ForwardOnlyWriter.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Utils -* File: ForwardOnlyBufferWriter.cs +* File: ForwardOnlyWriter.cs * -* ForwardOnlyBufferWriter.cs is part of VNLib.Utils which is part of the larger +* ForwardOnlyWriter.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 @@ -60,6 +60,18 @@ namespace VNLib.Utils.Memory } /// <summary> + /// Creates a new <see cref="ForwardOnlyWriter{T}"/> assigning the specified buffer + /// at the specified offset + /// </summary> + /// <param name="buffer">The buffer to write data to</param> + /// <param name="offset">The offset to begin the writer at</param> + public ForwardOnlyWriter(in Span<T> buffer, int offset) + { + Buffer = buffer[offset..]; + Written = 0; + } + + /// <summary> /// Returns a compiled string from the characters written to the buffer /// </summary> /// <returns>A string of the characters written to the buffer</returns> diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 2d51d2f..ee2677f 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -585,9 +585,41 @@ namespace VNLib.Utils.Memory #endregion + /// <summary> + /// Pins the supplied array and gets the memory handle that controls + /// the pinning lifetime via GC handle + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="array">The array to pin</param> + /// <param name="elementOffset">The address offset</param> + /// <returns>A <see cref="MemoryHandle"/> that manages the pinning of the supplied array</returns> + /// <exception cref="IndexOutOfRangeException"></exception> + public static MemoryHandle PinArrayAndGetHandle<T>(T[] array, int elementOffset) + { + //Quick verify index exists + _ = array[elementOffset]; + + //Pin the array + GCHandle arrHandle = GCHandle.Alloc(array, GCHandleType.Pinned); + //Get array base address + void* basePtr = (void*)arrHandle.AddrOfPinnedObject(); + //Get element offset + void* indexOffet = Unsafe.Add<T>(basePtr, elementOffset); + + return new(indexOffet, arrHandle); + } #region alloc + /// <summary> + /// Gets a <see cref="Span{T}"/> from the supplied address + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="address">The address of the begining of the memory sequence</param> + /// <param name="size">The size of the sequence</param> + /// <returns>The span pointing to the memory at the supplied addres</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> GetSpan<T>(IntPtr address, int size) => new(address.ToPointer(), size); /// <summary> /// Rounds the requested byte size up to the nearest page @@ -653,6 +685,29 @@ namespace VNLib.Utils.Memory /// <summary> /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + //Round to nearest page + nint np = NearestPage(elements); + return UnsafeAlloc<T>((int)np, zero); + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators. /// </summary> /// <typeparam name="T">The unamanged type to allocate</typeparam> @@ -681,6 +736,29 @@ namespace VNLib.Utils.Memory } } + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static IMemoryHandle<T> SafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + //Round to nearest page + nint np = NearestPage(elements); + return SafeAlloc<T>((int)np, zero); + } + #endregion } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs index 72edb26..6d566f1 100644 --- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs +++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -55,10 +55,10 @@ namespace VNLib.Utils.Memory private readonly int _length; ///<inheritdoc/> - public readonly unsafe Span<T> Span + public readonly Span<T> Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _handleType == HandleType.Pool ? _poolArr.AsSpan(0, IntLength) : new (_memoryPtr.ToPointer(), IntLength); + get => _handleType == HandleType.Pool ? _poolArr.AsSpan(0, IntLength) : MemoryUtil.GetSpan<T>(_memoryPtr, IntLength); } /// <summary> /// Gets the integer number of elements of the block of memory pointed to by this handle @@ -90,7 +90,7 @@ namespace VNLib.Utils.Memory /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public unsafe UnsafeMemoryHandle(ArrayPool<T> pool, int elements, bool zero) + public UnsafeMemoryHandle(ArrayPool<T> pool, int elements, bool zero) { if (elements < 0) { @@ -170,13 +170,7 @@ namespace VNLib.Utils.Memory if (_handleType == HandleType.Pool) { - //Pin the array - GCHandle arrHandle = GCHandle.Alloc(_poolArr, GCHandleType.Pinned); - //Get array base address - void* basePtr = (void*)arrHandle.AddrOfPinnedObject(); - //Get element offset - void* indexOffet = Unsafe.Add<T>(basePtr, elementIndex); - return new (indexOffet, arrHandle); + return MemoryUtil.PinArrayAndGetHandle(_poolArr!, elementIndex); } else { diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index 2ef8a41..d890757 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.UtilsTests @@ -22,7 +22,7 @@ * along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/. */ - +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using VNLib.Utils.Extensions; diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index d55817c..879c51e 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -1,4 +1,5 @@ -using System.Buffers; +using System; +using System.Buffers; using System.Runtime.InteropServices; using System.Security.Cryptography; diff --git a/lib/Utils/tests/Memory/VnTableTests.cs b/lib/Utils/tests/Memory/VnTableTests.cs index 6f9fbf2..06bcb13 100644 --- a/lib/Utils/tests/Memory/VnTableTests.cs +++ b/lib/Utils/tests/Memory/VnTableTests.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.UtilsTests @@ -22,6 +22,7 @@ * along with VNLib.UtilsTests. If not, see http://www.gnu.org/licenses/. */ +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/lib/Utils/tests/VNLib.UtilsTests.csproj b/lib/Utils/tests/VNLib.UtilsTests.csproj index 3a079c6..ca4f467 100644 --- a/lib/Utils/tests/VNLib.UtilsTests.csproj +++ b/lib/Utils/tests/VNLib.UtilsTests.csproj @@ -2,13 +2,9 @@ <PropertyGroup> <TargetFramework>net6.0</TargetFramework> - <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> - <IsPackable>false</IsPackable> - <AllowUnsafeBlocks>true</AllowUnsafeBlocks> - </PropertyGroup> <ItemGroup> @@ -20,7 +16,7 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="MSTest.TestAdapter" Version="3.0.2" /> <PackageReference Include="MSTest.TestFramework" Version="3.0.2" /> <PackageReference Include="coverlet.collector" Version="3.2.0"> diff --git a/lib/Utils/tests/VnEncodingTests.cs b/lib/Utils/tests/VnEncodingTests.cs index 373b834..f1ef5f4 100644 --- a/lib/Utils/tests/VnEncodingTests.cs +++ b/lib/Utils/tests/VnEncodingTests.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.UtilsTests @@ -23,6 +23,7 @@ */ using System; +using System.Linq; using System.Text; using System.Buffers; using System.Buffers.Text; |