From f57575471846e13060f5f01be8dc6c5ffcb905d2 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sat, 25 Feb 2023 21:08:27 -0500 Subject: Project meta + core features and upgrades --- lib/Utils/src/Async/AccessSerializer.cs | 297 --------------------- lib/Utils/src/Async/IAsyncAccessSerializer.cs | 53 ++++ lib/Utils/src/Extensions/CollectionExtensions.cs | 17 +- lib/Utils/src/Extensions/SerializerHandle.cs | 76 ++++++ lib/Utils/src/Extensions/ThreadingExtensions.cs | 45 +++- lib/Utils/src/IO/IBufferReader.cs | 50 ++++ lib/Utils/src/Memory/Caching/LRUCache.cs | 12 +- lib/Utils/src/Memory/Caching/ObjectRental.cs | 124 +++++---- .../src/Memory/Caching/ThreadLocalObjectStorage.cs | 11 +- lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs | 122 --------- lib/Utils/src/Memory/ForwardOnlyMemoryReader.cs | 2 +- lib/Utils/src/Memory/ForwardOnlyReader.cs | 16 +- lib/Utils/src/Memory/ForwardOnlyWriter.cs | 134 ++++++++++ lib/Utils/src/Memory/MemoryUtil.cs | 80 +++++- lib/Utils/src/Memory/UnsafeMemoryHandle.cs | 16 +- 15 files changed, 567 insertions(+), 488 deletions(-) delete mode 100644 lib/Utils/src/Async/AccessSerializer.cs create mode 100644 lib/Utils/src/Async/IAsyncAccessSerializer.cs create mode 100644 lib/Utils/src/Extensions/SerializerHandle.cs create mode 100644 lib/Utils/src/IO/IBufferReader.cs delete mode 100644 lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs create mode 100644 lib/Utils/src/Memory/ForwardOnlyWriter.cs (limited to 'lib/Utils/src') 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 -{ - /// - /// Provides access arbitration to an exclusive resouce - /// - /// The uinique identifier type for the resource - /// The resource type - public sealed class AccessSerializer where TResource : IExclusiveResource - { - private readonly SemaphoreSlim semaphore; - private readonly Func Factory; - private readonly Action CompletedCb; - private int WaitingCount; - /// - /// Creates a new with the specified factory and completed callback - /// - /// Factory function to genereate new objects from keys - /// Function to be invoked when the encapsulated objected is no longer in use - /// - public AccessSerializer(Func 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; - } - - /// - /// Attempts to obtain an exclusive lock on the object - /// - /// - /// Time to wait for lock - /// - /// true if lock was obtained within the timeout, false if the lock was not obtained - /// - /// - public bool TryWait(TKey key, TimeSpan wait, out ExclusiveResourceHandle 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); - } - } - /// - /// Waits for exclusive access to the resource. - /// - /// - /// An encapsulating the resource - public ExclusiveResourceHandle 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); - } - } - /// - /// Asynchronously waits for exclusive access to the resource. - /// - /// An encapsulating the resource - public async Task> 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); - } - } - /// - /// Releases an exclusive lock that is held on an object - /// - 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(); - } - } - } - - /// - /// Provides access arbitration to an - /// - /// The uinique identifier type for the resource - /// The type of the optional argument to be passed to the user-implemented factory function - /// The resource type - public sealed class AccessSerializer where TResource : IExclusiveResource - { - private readonly SemaphoreSlim semaphore; - private readonly Func Factory; - private readonly Action CompletedCb; - private int WaitingCount; - /// - /// Creates a new with the specified factory and completed callback - /// - /// Factory function to genereate new objects from keys - /// Function to be invoked when the encapsulated objected is no longer in use - /// - public AccessSerializer(Func 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; - } - - /// - /// Attempts to obtain an exclusive lock on the object - /// - /// - /// The key identifying the resource - /// Time to wait for lock - /// - /// true if lock was obtained within the timeout, false if the lock was not obtained - /// - /// - public bool TryWait(TKey key, TArg arg, TimeSpan wait, out ExclusiveResourceHandle 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); - } - } - /// - /// Waits for exclusive access to the resource. - /// - /// The unique key that identifies the resource - /// The state argument to pass to the factory function - /// An encapsulating the resource - public ExclusiveResourceHandle 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); - } - } - /// - /// Asynchronously waits for exclusive access to the resource. - /// - /// - /// The state argument to pass to the factory function - /// - /// An encapsulating the resource - public async Task> 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); - } - } - - /// - /// Releases an exclusive lock that is held on an object - /// - 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 +{ + /// + /// A mutual exclusion primitive that provides asynchronous waites for serialized + /// access to a resource based on a moniker. Similar to the + /// class. + /// + /// The moniker type, the uniuqe token identifying the wait + public interface IAsyncAccessSerializer + { + /// + /// Provides waiting for exclusve access identified + /// by the supplied moniker + /// + /// The moniker used to identify the wait + /// A token to cancel the async wait operation + /// A task that completes when the wait identified by the moniker is released + Task WaitAsync(TMoniker moniker, CancellationToken cancellation = default); + + /// + /// Completes the exclusive access identified by the moniker + /// + /// The moniker used to identify the wait to release + 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 { /// @@ -63,6 +64,10 @@ namespace VNLib.Utils.Extensions /// The value to serialze public static void SetValueType(this IIndexable 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 /// public static void TryForeach(this IEnumerable list, Action handler) { + if (list is null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (handler is null) + { + throw new ArgumentNullException(nameof(handler)); + } + List? 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 +{ + /// + /// Holds the exlcusive lock to the the + /// and releases the lock when the handle is disposed + /// + /// The moniker type + public readonly struct SerializerHandle : IDisposable, IEquatable> + { + private readonly IAsyncAccessSerializer _serializer; + private readonly TMoniker _moniker; + + /// + /// Inializes a new that is holding + /// a lock to the given by the + /// given + /// + /// The monike that referrences the entered lock + /// The serialzer this handle will release the lock on when disposed + public SerializerHandle(TMoniker moniker, IAsyncAccessSerializer serializer) + { + _moniker = moniker; + _serializer = serializer; + } + + /// + /// Releases the exclusive lock on the moinker back to the serializer; + /// + public readonly void Dispose() => _serializer.Release(_moniker); + + /// + public override bool Equals(object? obj) => obj is SerializerHandle sh && Equals(sh); + + /// + public override int GetHashCode() => _moniker?.GetHashCode() ?? 0; + + public static bool operator ==(SerializerHandle left, SerializerHandle right) => left.Equals(right); + + public static bool operator !=(SerializerHandle left, SerializerHandle right) => !(left == right); + + /// + public bool Equals(SerializerHandle 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); } } - + + /// + /// Waits for exlcusive access to the resource identified by the given moniker + /// and returns a handle that will release the lock when disposed. + /// + /// + /// + /// The moniker used to identify the lock + /// A token to cancel the wait operation + /// A task that resolves a handle that holds the lock information and releases the lock when disposed + public static Task> GetHandleAsync( + this IAsyncAccessSerializer serialzer, + TMoniker moniker, + CancellationToken cancellation = default + ) + { + //Wait async get handle + static async Task> AwaitHandle(Task wait, IAsyncAccessSerializer serialzer, TMoniker moniker) + { + await wait.ConfigureAwait(false); + return new SerializerHandle(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(moniker, serialzer)); + } + + //Wait async + return AwaitHandle(wait, serialzer, moniker); + } + /// /// Asynchronously waits to enter the while observing a /// 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 +{ + /// + /// 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 + /// + /// The buffer data type + public interface IBufferReader + { + /// + /// Advances the reader by the number of elements read + /// + /// The number of elements read + void Advance(int count); + + /// + /// Gets the current data segment to read + /// + /// The current data segment to read + ReadOnlySpan 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> 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 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; } + /// /// Invoked when a record is evicted from the cache /// 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 /// /// The data type to reuse - public class ObjectRental : ObjectRental, IObjectRental, ICacheHolder, IEnumerable where T: class + public class ObjectRental : ObjectRental, IObjectRental, ICacheHolder where T: class { /// /// The initial data-structure capacity if quota is not defined /// public const int INITIAL_STRUCTURE_SIZE = 50; - protected readonly SemaphoreSlim StorageLock; + protected readonly object StorageLock; protected readonly Stack Storage; protected readonly HashSet 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 /// 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(); + } } + /// + /// Gets all the elements in the store as a "snapshot" + /// while holding the lock + /// + /// + protected T[] GetElementsWithLock() + { + T[] result; + + lock (StorageLock) + { + //Enumerate all items to the array + result = Storage.ToArray(); + } + + return result; + } + + /// /// 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(); + } } /// 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 } } - /// - public IEnumerator 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; - } - } + /// + /// Gets all the elements in the store currently. + /// + /// The current elements in storage as a "snapshot" + 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); } - + + /// + public override T[] GetItems() + { + return Store.Values.ToArray(); + } + /// protected override void Free() { diff --git a/lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs b/lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs deleted file mode 100644 index 0ea507e..0000000 --- a/lib/Utils/src/Memory/ForwardOnlyBufferWriter.cs +++ /dev/null @@ -1,122 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: ForwardOnlyBufferWriter.cs -* -* ForwardOnlyBufferWriter.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 -{ - /// - /// Provides a stack based buffer writer - /// - public ref struct ForwardOnlyWriter - { - /// - /// The buffer for writing output data to - /// - public readonly Span Buffer { get; } - /// - /// The number of characters written to the buffer - /// - public int Written { readonly get; set; } - /// - /// The number of characters remaining in the buffer - /// - public readonly int RemainingSize => Buffer.Length - Written; - - /// - /// The remaining buffer window - /// - public readonly Span Remaining => Buffer[Written..]; - - /// - /// Creates a new assigning the specified buffer - /// - /// The buffer to write data to - public ForwardOnlyWriter(in Span buffer) - { - Buffer = buffer; - Written = 0; - } - - /// - /// Returns a compiled string from the characters written to the buffer - /// - /// A string of the characters written to the buffer - public readonly override string ToString() => Buffer[..Written].ToString(); - - /// - /// Appends a sequence to the buffer - /// - /// The data to append to the buffer - /// - public void Append(ReadOnlySpan data) - { - //Make sure the current window is large enough to buffer the new string - if (data.Length > RemainingSize) - { - throw new ArgumentOutOfRangeException(nameof(Remaining) ,"The internal buffer does not have enough buffer space"); - } - Span window = Buffer[Written..]; - //write data to window - data.CopyTo(window); - //update char position - Written += data.Length; - } - /// - /// Appends a single item to the buffer - /// - /// The item to append to the buffer - /// - public void Append(T c) - { - //Make sure the current window is large enough to buffer the new string - if (RemainingSize == 0) - { - throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); - } - //Write data to buffer and increment the buffer position - Buffer[Written++] = c; - } - - /// - /// Advances the writer forward the specifed number of elements - /// - /// The number of elements to advance the writer by - /// - public void Advance(int count) - { - if (count > RemainingSize) - { - throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); - } - Written += count; - } - - /// - /// Resets the writer by setting the - /// property to 0. - /// - public void Reset() => Written = 0; - } -} 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; /// - /// Initializes a new + /// Initializes a new /// of the specified type using the specified internal buffer /// /// The buffer to read from 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; /// - /// Initializes a new + /// Initializes a new /// of the specified type using the specified internal buffer /// /// The buffer to read from @@ -50,6 +50,20 @@ namespace VNLib.Utils.Memory _position = 0; } + /// + /// Initializes a new + /// of the specified type using the specified internal buffer + /// begining at the specified offset + /// + /// The buffer to read from + /// The offset within the supplied buffer to begin the reader at + public ForwardOnlyReader(in ReadOnlySpan buffer, int offset) + { + _segment = buffer[offset..]; + _size = _segment.Length; + _position = 0; + } + /// /// The remaining data window /// diff --git a/lib/Utils/src/Memory/ForwardOnlyWriter.cs b/lib/Utils/src/Memory/ForwardOnlyWriter.cs new file mode 100644 index 0000000..efd7f2b --- /dev/null +++ b/lib/Utils/src/Memory/ForwardOnlyWriter.cs @@ -0,0 +1,134 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: ForwardOnlyWriter.cs +* +* 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 +* 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 +{ + /// + /// Provides a stack based buffer writer + /// + public ref struct ForwardOnlyWriter + { + /// + /// The buffer for writing output data to + /// + public readonly Span Buffer { get; } + /// + /// The number of characters written to the buffer + /// + public int Written { readonly get; set; } + /// + /// The number of characters remaining in the buffer + /// + public readonly int RemainingSize => Buffer.Length - Written; + + /// + /// The remaining buffer window + /// + public readonly Span Remaining => Buffer[Written..]; + + /// + /// Creates a new assigning the specified buffer + /// + /// The buffer to write data to + public ForwardOnlyWriter(in Span buffer) + { + Buffer = buffer; + Written = 0; + } + + /// + /// Creates a new assigning the specified buffer + /// at the specified offset + /// + /// The buffer to write data to + /// The offset to begin the writer at + public ForwardOnlyWriter(in Span buffer, int offset) + { + Buffer = buffer[offset..]; + Written = 0; + } + + /// + /// Returns a compiled string from the characters written to the buffer + /// + /// A string of the characters written to the buffer + public readonly override string ToString() => Buffer[..Written].ToString(); + + /// + /// Appends a sequence to the buffer + /// + /// The data to append to the buffer + /// + public void Append(ReadOnlySpan data) + { + //Make sure the current window is large enough to buffer the new string + if (data.Length > RemainingSize) + { + throw new ArgumentOutOfRangeException(nameof(Remaining) ,"The internal buffer does not have enough buffer space"); + } + Span window = Buffer[Written..]; + //write data to window + data.CopyTo(window); + //update char position + Written += data.Length; + } + /// + /// Appends a single item to the buffer + /// + /// The item to append to the buffer + /// + public void Append(T c) + { + //Make sure the current window is large enough to buffer the new string + if (RemainingSize == 0) + { + throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); + } + //Write data to buffer and increment the buffer position + Buffer[Written++] = c; + } + + /// + /// Advances the writer forward the specifed number of elements + /// + /// The number of elements to advance the writer by + /// + public void Advance(int count) + { + if (count > RemainingSize) + { + throw new ArgumentOutOfRangeException(nameof(Remaining), "The internal buffer does not have enough buffer space"); + } + Written += count; + } + + /// + /// Resets the writer by setting the + /// property to 0. + /// + public void Reset() => Written = 0; + } +} 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 + /// + /// Pins the supplied array and gets the memory handle that controls + /// the pinning lifetime via GC handle + /// + /// + /// The array to pin + /// The address offset + /// A that manages the pinning of the supplied array + /// + public static MemoryHandle PinArrayAndGetHandle(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(basePtr, elementOffset); + + return new(indexOffet, arrHandle); + } #region alloc + /// + /// Gets a from the supplied address + /// + /// + /// The address of the begining of the memory sequence + /// The size of the sequence + /// The span pointing to the memory at the supplied addres + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span GetSpan(IntPtr address, int size) => new(address.ToPointer(), size); /// /// Rounds the requested byte size up to the nearest page @@ -651,6 +683,29 @@ namespace VNLib.Utils.Memory } } + /// + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// + /// The unamanged type to allocate + /// The number of elements of the type within the block + /// Flag to zero elements during allocation before the method returns + /// A handle to the block of memory + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeMemoryHandle UnsafeAllocNearestPage(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((int)np, zero); + } + /// /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators. @@ -681,6 +736,29 @@ namespace VNLib.Utils.Memory } } + /// + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// + /// The unamanged type to allocate + /// The number of elements of the type within the block + /// Flag to zero elements during allocation before the method returns + /// A handle to the block of memory + /// + /// + public static IMemoryHandle SafeAllocNearestPage(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((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; /// - public readonly unsafe Span Span + public readonly Span 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(_memoryPtr, IntLength); } /// /// Gets the integer number of elements of the block of memory pointed to by this handle @@ -90,7 +90,7 @@ namespace VNLib.Utils.Memory /// /// /// - public unsafe UnsafeMemoryHandle(ArrayPool pool, int elements, bool zero) + public UnsafeMemoryHandle(ArrayPool 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(basePtr, elementIndex); - return new (indexOffet, arrHandle); + return MemoryUtil.PinArrayAndGetHandle(_poolArr!, elementIndex); } else { -- cgit