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