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