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 --- .../src/Argon2/Argon2PasswordEntry.cs | 20 +- lib/Hashing.Portable/src/Argon2/VnArgon2.cs | 2 +- lib/Net.Messaging.FBM/src/FBMMessageHeader.cs | 7 +- lib/Net.Messaging.FBM/src/Helpers.cs | 2 +- lib/Net.Transport.SimpleTCP/README.md | 12 +- .../src/Sessions/SessionHandle.cs | 19 +- .../src/VNLib.Plugins.Essentials.csproj | 4 +- 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 +- lib/Utils/tests/Memory/MemoryHandleTest.cs | 4 +- lib/Utils/tests/Memory/MemoryUtilTests.cs | 3 +- lib/Utils/tests/Memory/VnTableTests.cs | 3 +- lib/Utils/tests/VNLib.UtilsTests.csproj | 6 +- lib/Utils/tests/VnEncodingTests.cs | 3 +- 27 files changed, 611 insertions(+), 529 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') diff --git a/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs b/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs index bc57d7a..b45cceb 100644 --- a/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs +++ b/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs @@ -22,16 +22,6 @@ * along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/. */ -/* - * VnArgon2.cs - * Author: Vaughhn Nugent - * Date: July 17, 2021 - * - * Dependencies Argon2. - * https://github.com/P-H-C/phc-winner-argon2 - * - */ - using System; using System.Globalization; @@ -54,7 +44,7 @@ namespace VNLib.Hashing private static Argon2_version ParseVersion(ReadOnlySpan window) { //Version comes after the v= prefix - ReadOnlySpan v = window.SliceAfterParam(",v="); + ReadOnlySpan v = window.SliceAfterParam("v="); v = v.SliceBeforeParam(','); //Parse the version as an enum value return Enum.Parse(v); @@ -63,7 +53,7 @@ namespace VNLib.Hashing private static uint ParseTimeCost(ReadOnlySpan window) { //TimeCost comes after the t= prefix - ReadOnlySpan t = window.SliceAfterParam(",t="); + ReadOnlySpan t = window.SliceAfterParam("t="); t = t.SliceBeforeParam(','); //Parse the time cost as an unsigned integer return uint.Parse(t, NumberStyles.Integer, CultureInfo.InvariantCulture); @@ -72,7 +62,7 @@ namespace VNLib.Hashing private static uint ParseMemoryCost(ReadOnlySpan window) { //MemoryCost comes after the m= prefix - ReadOnlySpan m = window.SliceAfterParam(",m="); + ReadOnlySpan m = window.SliceAfterParam("m="); m = m.SliceBeforeParam(','); //Parse the memory cost as an unsigned integer return uint.Parse(m, NumberStyles.Integer, CultureInfo.InvariantCulture); @@ -81,7 +71,7 @@ namespace VNLib.Hashing private static uint ParseParallelism(ReadOnlySpan window) { //Parallelism comes after the p= prefix - ReadOnlySpan p = window.SliceAfterParam(",p="); + ReadOnlySpan p = window.SliceAfterParam("p="); p = p.SliceBeforeParam(','); //Parse the parallelism as an unsigned integer return uint.Parse(p, NumberStyles.Integer, CultureInfo.InvariantCulture); @@ -90,7 +80,7 @@ namespace VNLib.Hashing private static ReadOnlySpan ParseSalt(ReadOnlySpan window) { //Salt comes after the s= prefix - ReadOnlySpan s = window.SliceAfterParam(",s="); + ReadOnlySpan s = window.SliceAfterParam("s="); s = s.SliceBeforeParam('$'); //Parse the salt as a string return s; diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index 7b467ba..5963ca7 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -217,7 +217,7 @@ namespace VNLib.Hashing salts = Convert.ToBase64String(salt); //Encode salt in base64 - return $"${ID_MODE},v={(int)Argon2_version.VERSION_13},m={memCost},t={timeCost},p={parallelism},s={salts}${hash}"; + return $"${ID_MODE}$v={(int)Argon2_version.VERSION_13},m={memCost},t={timeCost},p={parallelism},s={salts}${hash}"; } /// diff --git a/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs index 3d345ef..d1f4f1c 100644 --- a/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs +++ b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Messaging.FBM @@ -97,5 +97,10 @@ namespace VNLib.Net.Messaging.FBM /// The other header to compare /// True if both headers have the same commad and value sequence public bool Equals(FBMMessageHeader other) => Header == other.Header && Value.SequenceEqual(other.Value); + + /// + /// Gets a concatinated string of the current instance for debugging purposes + /// + public readonly override string ToString() => $"{Header}:{Value.ToString()}"; } } diff --git a/lib/Net.Messaging.FBM/src/Helpers.cs b/lib/Net.Messaging.FBM/src/Helpers.cs index 14900ee..cce1d27 100644 --- a/lib/Net.Messaging.FBM/src/Helpers.cs +++ b/lib/Net.Messaging.FBM/src/Helpers.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Messaging.FBM diff --git a/lib/Net.Transport.SimpleTCP/README.md b/lib/Net.Transport.SimpleTCP/README.md index e61e1ba..3ced963 100644 --- a/lib/Net.Transport.SimpleTCP/README.md +++ b/lib/Net.Transport.SimpleTCP/README.md @@ -4,10 +4,10 @@ _A managed .NET simple, high performance - single process, low/no allocation, fu This library was created for use with the VNLib.Net.Http library and subsequent stacked framework libraries, however it was designed to be useful as a standalone high-performance .NET tcp listener. This library relies on the managed .NET [System.IO.Pipelines](https://github.com/dotnet/docs/blob/main/docs/standard/io/pipelines.md) library, and the **VNLib.Utils** library. -#### Builds +### Builds Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my [website](https://www.vaughnnugent.com/resources/software). All tar-gzip (.tgz) files will have an associated .sha384 appended checksum of the desired download file. -##### SSL Support +#### SSL Support The TcpServer manages ssl/tls using the SslStream class to make tls as transparent to the application as possible. The server manages authentication and negotiation based on the configured `SslServerAuthenticationOptions` ## Usage @@ -16,7 +16,7 @@ The TcpServer manages ssl/tls using the SslStream class to make tls as transpare //Init config TCPConfig config = new() { - ... configure + //... configure } //Create the new server @@ -32,9 +32,9 @@ The TcpServer manages ssl/tls using the SslStream class to make tls as transpare try { - ..Do stuff with context, such as read data from stream + //...Do stuff with context, such as read data from stream byte[] buffer = new byte [1024]; - int count = await ctx.ConnectionStream,ReadAsync(buffer) + int count = await ctx.ConnectionStream.ReadAsync(buffer) } finally { @@ -51,7 +51,7 @@ Internal buffers are allocated for reading and writing to the internal socket. R so if you wish to reduce socket memory consumption, you may use the `TCPConfig.OnSocketCreated` callback method to configure your socket accordingly. ##### Threading -This library uses the SocketAsyncEventArgs WinSock socket programming paradigm, so the `TPCConfig.AcceptThread` configuration property is the number of outstanding SocketAsyncEvents that will be pending. This value should be tuned to your use case, lower numbers relative to processor count may yield less accepts/second, higher numbers may see no increase or even reduced performance. +This library uses the SocketAsyncEventArgs WinSock socket programming paradigm, so the `TCPConfig.AcceptThreads` configuration property is the number of outstanding SocketAsyncEvents that will be pending. This value should be tuned to your use case, lower numbers relative to processor count may yield less accepts/second, higher numbers may see no increase or even reduced performance. ##### Internal object cache TcpServer maintains a complete object cache (VNLib.Utils.Memory.Caching.ObjectCache) which may grow quite large for your application depending on load, tuning the cache quota config property may be useful for your application. Lower numbers will increase GC load, higher values (or disabled) will likely yield a larger working set. Because of this the TcpServer class implements the ICacheHolder interface. **Note:** because TcpServer caches store disposable objects, the `CacheClear()` method does nothing. To programatically clear these caches, call the `CacheHardClear()` method. diff --git a/lib/Plugins.Essentials/src/Sessions/SessionHandle.cs b/lib/Plugins.Essentials/src/Sessions/SessionHandle.cs index 15c2743..8dbb077 100644 --- a/lib/Plugins.Essentials/src/Sessions/SessionHandle.cs +++ b/lib/Plugins.Essentials/src/Sessions/SessionHandle.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials @@ -32,6 +32,12 @@ using VNLib.Net.Http; namespace VNLib.Plugins.Essentials.Sessions { + /// + /// The callback method signature used to release attached sessions + /// + /// The session being released + /// The connection the session is attached to + /// A value task that resolves when the session has been released from the connection public delegate ValueTask SessionReleaseCallback(ISession session, IHttpEvent @event); /// @@ -47,6 +53,9 @@ namespace VNLib.Plugins.Essentials.Sessions private readonly SessionReleaseCallback? ReleaseCb; + /// + /// True when a valid session is held by the current handle + /// internal readonly bool IsSet => SessionData != null; /// @@ -83,7 +92,7 @@ namespace VNLib.Plugins.Essentials.Sessions /// Releases the session from use /// /// The current connection event object - public ValueTask ReleaseAsync(IHttpEvent @event) => ReleaseCb?.Invoke(SessionData!, @event) ?? ValueTask.CompletedTask; + public readonly ValueTask ReleaseAsync(IHttpEvent @event) => ReleaseCb?.Invoke(SessionData!, @event) ?? ValueTask.CompletedTask; /// /// Determines if another is equal to the current handle. @@ -91,15 +100,15 @@ namespace VNLib.Plugins.Essentials.Sessions /// /// The other handle to /// true if neither handle is set or if their SessionData object is equal, false otherwise - public bool Equals(SessionHandle other) + public readonly bool Equals(SessionHandle other) { //If neither handle is set, then they are equal, otherwise they are equal if the session objects themselves are equal return (!IsSet && !other.IsSet) || (SessionData?.Equals(other.SessionData) ?? false); } /// - public override bool Equals([NotNullWhen(true)] object? obj) => (obj is SessionHandle other) && Equals(other); + public readonly override bool Equals([NotNullWhen(true)] object? obj) => (obj is SessionHandle other) && Equals(other); /// - public override int GetHashCode() + public readonly override int GetHashCode() { return IsSet ? SessionData!.GetHashCode() : base.GetHashCode(); } diff --git a/lib/Plugins.Essentials/src/VNLib.Plugins.Essentials.csproj b/lib/Plugins.Essentials/src/VNLib.Plugins.Essentials.csproj index 1fa6264..fd558ec 100644 --- a/lib/Plugins.Essentials/src/VNLib.Plugins.Essentials.csproj +++ b/lib/Plugins.Essentials/src/VNLib.Plugins.Essentials.csproj @@ -21,9 +21,9 @@ Provides essential web, sessions, users abstractions for building extensable web applications with satefull sessions, user based intraction with login and account security extensions. - https://www.vaughnnugent.com/resources/software + https://www.vaughnnugent.com/resources/software/modules/VNLib.Core VNLib, Plugins, VNLib.Plugins.Essentials, Essentials, Essential Plugins, HTTP Essentials, OAuth2 - https://github.com/VnUgE/VNLib.Core + https://github.com/VnUgE/VNLib.Core/tree/main/lib/Plugins.Essentials 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 { 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 @@ net6.0 - enable enable - false - true - @@ -20,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + 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; -- cgit