aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-11-02 01:49:02 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-11-02 01:49:02 -0400
commit9e3dd9be0f0ec7aaef1a719f09f96425e66369df (patch)
tree59b8bd4ace8750327db80823fa1e5eccdf44bc74
parenteafefadc4b858e5b5be481662a2b0c8e47a43bf4 (diff)
may have gottem carried away
-rw-r--r--lib/Net.Compression/VNLib.Net.CompressionTests/VNLib.Net.CompressionTests.csproj2
-rw-r--r--lib/Net.Compression/vnlib_compress/CMakeLists.txt2
-rw-r--r--lib/Net.Compression/vnlib_compress/Taskfile.yaml40
-rw-r--r--lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems3
-rw-r--r--lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs4
-rw-r--r--lib/Net.Http/src/Core/TransportReader.cs76
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs94
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClient.cs117
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs3
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMRequest.cs131
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMResponse.cs9
-rw-r--r--lib/Net.Messaging.FBM/src/Client/IFBMMessageWaiter.cs6
-rw-r--r--lib/Net.Messaging.FBM/src/Client/ManagedClientWebSocket.cs4
-rw-r--r--lib/Net.Messaging.FBM/src/FBMMessageHeader.cs1
-rw-r--r--lib/Net.Messaging.FBM/src/FallbackFBMMemoryManager.cs140
-rw-r--r--lib/Net.Messaging.FBM/src/IFBMMemoryHandle.cs43
-rw-r--r--lib/Net.Messaging.FBM/src/IFBMMemoryManager.cs71
-rw-r--r--lib/Net.Messaging.FBM/src/IFBMMessage.cs (renamed from lib/Net.Messaging.FBM/src/Client/IFBMMessage.cs)4
-rw-r--r--lib/Net.Messaging.FBM/src/IFBMSpanOnlyMemoryHandle.cs42
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMContext.cs9
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListener.cs108
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListenerBase.cs81
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs4
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs56
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs82
-rw-r--r--lib/Net.Messaging.FBM/src/Server/HeaderDataAccumulator.cs32
-rw-r--r--lib/Net.Messaging.FBM/src/Server/IAsyncMessageReader.cs4
-rw-r--r--lib/Net.Messaging.FBM/src/Server/IFBMServerErrorHandler.cs49
-rw-r--r--lib/Net.Messaging.FBM/src/Server/IFBMServerMessageHandler.cs43
-rw-r--r--lib/Net.Messaging.FBM/src/VNLib.Net.Messaging.FBM.csproj1
-rw-r--r--lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs60
-rw-r--r--lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs4
-rw-r--r--lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs2
-rw-r--r--lib/Plugins.Essentials/src/Middleware/IHttpMiddlewareChain.cs8
-rw-r--r--lib/Plugins.Essentials/src/Middleware/MiddlewareImplAttribute.cs48
-rw-r--r--lib/Plugins.Essentials/src/Middleware/MiddlewareImplOptions.cs46
-rw-r--r--lib/Plugins.Essentials/src/Middleware/SemiConistentMiddlewareChain.cs24
-rw-r--r--lib/Plugins.PluginBase/src/PluginBase.cs17
-rw-r--r--lib/Utils.Memory/vnlib_mimalloc/Taskfile.yaml2
-rw-r--r--lib/Utils/src/Async/IAsyncEventSink.cs48
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs140
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs87
-rw-r--r--lib/Utils/src/Memory/ArrayPoolBuffer.cs (renamed from lib/Utils/src/Memory/VnTempBuffer.cs)89
-rw-r--r--lib/Utils/src/Memory/Caching/IReusable.cs10
-rw-r--r--lib/Utils/src/Memory/HeapCreation.cs6
-rw-r--r--lib/Utils/src/Memory/IMemoryHandle.cs8
-rw-r--r--lib/Utils/src/Memory/IResizeableMemoryHandle.cs52
-rw-r--r--lib/Utils/src/Memory/MemoryHandle.cs61
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs300
-rw-r--r--lib/Utils/src/Memory/MemoryUtilAlloc.cs8
-rw-r--r--lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs6
-rw-r--r--lib/Utils/src/Memory/ProcessHeap.cs2
-rw-r--r--lib/Utils/src/Memory/SubSequence.cs6
-rw-r--r--lib/Utils/src/Memory/SysBufferMemoryManager.cs46
-rw-r--r--lib/Utils/src/Memory/UnmanagedHeapBase.cs29
-rw-r--r--lib/Utils/src/Memory/UnsafeMemoryHandle.cs24
-rw-r--r--lib/Utils/src/Memory/VnString.cs14
-rw-r--r--lib/Utils/src/Memory/Win32PrivateHeap.cs3
-rw-r--r--lib/Utils/src/Resources/ManagedLibrary.cs138
-rw-r--r--lib/Utils/tests/Memory/MemoryHandleTest.cs5
-rw-r--r--lib/Utils/tests/VNLib.UtilsTests.csproj2
61 files changed, 1734 insertions, 822 deletions
diff --git a/lib/Net.Compression/VNLib.Net.CompressionTests/VNLib.Net.CompressionTests.csproj b/lib/Net.Compression/VNLib.Net.CompressionTests/VNLib.Net.CompressionTests.csproj
index adf9496..56c67ed 100644
--- a/lib/Net.Compression/VNLib.Net.CompressionTests/VNLib.Net.CompressionTests.csproj
+++ b/lib/Net.Compression/VNLib.Net.CompressionTests/VNLib.Net.CompressionTests.csproj
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
diff --git a/lib/Net.Compression/vnlib_compress/CMakeLists.txt b/lib/Net.Compression/vnlib_compress/CMakeLists.txt
index 90fe75d..a5dfaa3 100644
--- a/lib/Net.Compression/vnlib_compress/CMakeLists.txt
+++ b/lib/Net.Compression/vnlib_compress/CMakeLists.txt
@@ -11,7 +11,7 @@ set(VNLIB_COMPRESS_SOURCES compression.c)
#set options for enable botli and zlib
option(ENABLE_BROTLI "Enable brotli compression" ON)
option(ENABLE_ZLIB "Enable zlib compression" ON)
-option(ENABLE_RPMALLOC "Link local vnlib_rpmalloc allocator" ON)
+option(ENABLE_RPMALLOC "Link local vnlib_rpmalloc allocator" OFF)
#add feature specific source files to the project
if(ENABLE_BROTLI)
diff --git a/lib/Net.Compression/vnlib_compress/Taskfile.yaml b/lib/Net.Compression/vnlib_compress/Taskfile.yaml
index 51aaf79..74ab24a 100644
--- a/lib/Net.Compression/vnlib_compress/Taskfile.yaml
+++ b/lib/Net.Compression/vnlib_compress/Taskfile.yaml
@@ -8,14 +8,50 @@
version: '3'
+vars:
+ THIRD_PARTY_DIR: '../third-party'
+ PROJECT_NAME: 'vnlib_compress'
+
tasks:
-
+
+ default:
+ cmds:
+ - cmd: echo "Building vnlib_compress"
+ silent: true
+
+ #make dirs on non-win
+ - cmd: mkdir {{.THIRD_PARTY_DIR}}
+ platforms: ['linux', 'darwin']
+
+ #make dirs on windows
+ - cmd: powershell -Command "mkdir {{.THIRD_PARTY_DIR}} -Force"
+ platforms: ['windows']
+
+ #clone libs
+ - cmd: cd {{.THIRD_PARTY_DIR}} && git clone https://github.com/cloudflare/zlib.git
+ ignore_error: true
+
+ - cmd: cd {{.THIRD_PARTY_DIR}} && git clone https://github.com/google/brotli.git
+ ignore_error: true
+
+ #invoke cmake for build
+ - cmake -B./build -DCMAKE_BUILD_TYPE=RELEASE {{.CMAKE_ARGS}}
+
+ #build for Windows
+ - cmd: cd build && msbuild {{.PROJECT_NAME}}.sln /p:Configuration=release {{.BUILD_FLAGS}}
+ platforms: ['windows']
+
+ #using make
+ - cmd: cd build && make
+ platforms: ['linux', 'darwin']
+
+
#when build succeeds, archive the output into a tgz
postbuild_success:
cmds:
- cmd: powershell mkdir -Force './bin'
#copy source code to target
- - powershell -Command "Get-ChildItem -Include *.c,*.h,*.txt -Path * | Resolve-Path -Relative | tar --files-from - -czf 'bin/src.tgz'"
+ - powershell -Command "tar --exclude build/* --exclude .vs/* --exclude bin/* -czvf bin/src.tgz ."
postbuild_failed:
cmds: []
diff --git a/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems b/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems
index 9249ad9..0bfdcbf 100644
--- a/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems
+++ b/lib/Net.Compression/vnlib_compress/vnlib_compress.vcxitems
@@ -27,4 +27,7 @@
<ItemGroup>
<Text Include="$(MSBuildThisFileDirectory)CMakeLists.txt" />
</ItemGroup>
+ <ItemGroup>
+ <None Include="$(MSBuildThisFileDirectory)Taskfile.yaml" />
+ </ItemGroup>
</Project> \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs b/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
index e65ffd8..103d723 100644
--- a/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
+++ b/lib/Net.Http/src/Core/Buffering/SplitHttpBufferElement.cs
@@ -26,6 +26,8 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using VNLib.Utils.Memory;
+
namespace VNLib.Net.Http.Core.Buffering
{
internal abstract class SplitHttpBufferElement : HttpBufferElement, ISplitHttpBuffer
@@ -65,6 +67,6 @@ namespace VNLib.Net.Http.Core.Buffering
/// </summary>
/// <param name="binSize">The desired size of the binary buffer</param>
/// <returns>The total size of the binary buffer required to store the binary and character buffer</returns>
- public static int GetfullSize(int binSize) => binSize + (binSize * sizeof(char));
+ public static int GetfullSize(int binSize) => binSize + MemoryUtil.ByteCount<char>(binSize);
}
}
diff --git a/lib/Net.Http/src/Core/TransportReader.cs b/lib/Net.Http/src/Core/TransportReader.cs
index 58c23df..8d605d1 100644
--- a/lib/Net.Http/src/Core/TransportReader.cs
+++ b/lib/Net.Http/src/Core/TransportReader.cs
@@ -25,6 +25,9 @@
using System;
using System.IO;
using System.Text;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
using VNLib.Utils;
using VNLib.Utils.IO;
@@ -36,8 +39,17 @@ namespace VNLib.Net.Http.Core
/// <summary>
/// Structure implementation of <see cref="IVnTextReader"/>
/// </summary>
- internal struct TransportReader : IVnTextReader
+ internal readonly struct TransportReader : IVnTextReader
{
+ /*
+ * To make this structure read-only we can store the
+ * mutable values in a private segment of the internal
+ * buffer. 8 bytes are reserved at the beining and an
+ * additional word is added for padding incase small/wild
+ * under/over run occurs.
+ */
+ const int PrivateBufferOffset = 4 * sizeof(int);
+
///<inheritdoc/>
public readonly Encoding Encoding { get; }
@@ -47,10 +59,25 @@ namespace VNLib.Net.Http.Core
///<inheritdoc/>
public readonly Stream BaseStream { get; }
+ /*
+ * Store the window start/end in the begging of the
+ * data buffer. Then use a constant offset to get the
+ * start of the buffer
+ */
+ private readonly int BufWindowStart
+ {
+ get => MemoryMarshal.Read<int>(Buffer.GetBinSpan());
+ set => MemoryMarshal.Write(Buffer.GetBinSpan(), ref value);
+ }
+
+ private readonly int BufWindowEnd
+ {
+ get => MemoryMarshal.Read<int>(Buffer.GetBinSpan()[sizeof(int)..]);
+ set => MemoryMarshal.Write(Buffer.GetBinSpan()[sizeof(int)..], ref value);
+ }
+
private readonly IHttpHeaderParseBuffer Buffer;
-
- private int BufWindowStart;
- private int BufWindowEnd;
+ private readonly int MAxBufferSize;
/// <summary>
/// Initializes a new <see cref="TransportReader"/> for reading text lines from the transport stream
@@ -61,29 +88,50 @@ namespace VNLib.Net.Http.Core
/// <param name="lineTermination">The line delimiter to search for</param>
public TransportReader(Stream transport, IHttpHeaderParseBuffer buffer, Encoding encoding, ReadOnlyMemory<byte> lineTermination)
{
- BufWindowEnd = 0;
- BufWindowStart = 0;
Encoding = encoding;
BaseStream = transport;
LineTermination = lineTermination;
Buffer = buffer;
+ MAxBufferSize = buffer.BinSize - PrivateBufferOffset;
+
+ //Initialize the buffer window
+ SafeZeroPrivateSegments(Buffer);
+
+ Debug.Assert(BufWindowEnd == 0 && BufWindowStart == 0);
}
+
+ /// <summary>
+ /// Clears the initial window start/end values with the
+ /// extra padding
+ /// </summary>
+ /// <param name="buffer">The buffer segment to initialize</param>
+ private static void SafeZeroPrivateSegments(IHttpHeaderParseBuffer buffer)
+ {
+ ref byte start = ref MemoryMarshal.GetReference(buffer.GetBinSpan());
+ Unsafe.InitBlock(ref start, 0, PrivateBufferOffset);
+ }
+
+ /// <summary>
+ /// Gets the data segment of the buffer after the private segment
+ /// </summary>
+ /// <returns></returns>
+ private readonly Span<byte> GetDataSegment() => Buffer.GetBinSpan()[PrivateBufferOffset..];
///<inheritdoc/>
public readonly int Available => BufWindowEnd - BufWindowStart;
///<inheritdoc/>
- public readonly Span<byte> BufferedDataWindow => Buffer.GetBinSpan()[BufWindowStart..BufWindowEnd];
+ public readonly Span<byte> BufferedDataWindow => GetDataSegment()[BufWindowStart..BufWindowEnd];
///<inheritdoc/>
- public void Advance(int count) => BufWindowStart += count;
+ public readonly void Advance(int count) => BufWindowStart += count;
///<inheritdoc/>
- public void FillBuffer()
+ public readonly void FillBuffer()
{
//Get a buffer from the end of the current window to the end of the buffer
- Span<byte> bufferWindow = Buffer.GetBinSpan()[BufWindowEnd..];
+ Span<byte> bufferWindow = GetDataSegment()[BufWindowEnd..];
//Read from stream
int read = BaseStream.Read(bufferWindow);
@@ -93,15 +141,15 @@ namespace VNLib.Net.Http.Core
}
///<inheritdoc/>
- public ERRNO CompactBufferWindow()
+ public readonly ERRNO CompactBufferWindow()
{
//No data to compact if window is not shifted away from start
if (BufWindowStart > 0)
{
//Get span over engire buffer
- Span<byte> buffer = Buffer.GetBinSpan();
+ Span<byte> buffer = GetDataSegment();
- //Get data within window
+ //Get used data segment within window
Span<byte> usedData = buffer[BufWindowStart..BufWindowEnd];
//Copy remaining to the begining of the buffer
@@ -115,7 +163,7 @@ namespace VNLib.Net.Http.Core
}
//Return the number of bytes of available space from the end of the current window
- return Buffer.BinSize - BufWindowEnd;
+ return MAxBufferSize - BufWindowEnd;
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs b/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs
index fac41a6..698a98b 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -28,28 +28,50 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using VNLib.Utils.IO;
+using VNLib.Utils.Memory.Caching;
namespace VNLib.Net.Messaging.FBM.Client
{
/// <summary>
/// Represents a shared internal character and bianry buffer for
/// </summary>
- internal sealed class FBMBuffer : IFBMHeaderBuffer, IDisposable
+ internal sealed class FBMBuffer : IFBMHeaderBuffer, IDisposable, IReusable
{
- private readonly IMemoryOwner<byte> Handle;
+ private readonly IFBMMemoryHandle Handle;
+ private readonly IFBMMemoryManager _memoryManager;
+ private readonly int _size;
private readonly BufferWriter _writer;
private readonly BinaryRequestAccumulator _requestAccumulator;
- internal FBMBuffer(IMemoryOwner<byte> handle)
+ internal FBMBuffer(IFBMMemoryManager manager, int bufferSize)
{
- Handle = handle;
+ _memoryManager = manager;
+ Handle = manager.InitHandle();
_writer = new(this);
- _requestAccumulator = new(handle.Memory);
+ _size = bufferSize;
+ _requestAccumulator = new(this, bufferSize);
}
+ /*
+ * Reusable methods will alloc and free buffers during
+ * normal operation.
+ */
+
+ ///<inheritdoc/>
+ public void Prepare() => _memoryManager.AllocBuffer(Handle, _size);
+
+ ///<inheritdoc/>
+ public bool Release()
+ {
+ _memoryManager.FreeBuffer(Handle);
+ return true;
+ }
+
+ public void Dispose() => _ = Release();
+
/// <summary>
/// Gets the internal request data accumulator
/// </summary>
@@ -73,13 +95,6 @@ namespace VNLib.Net.Messaging.FBM.Client
//Return the internal writer
return _writer;
}
-
-
- public void Dispose()
- {
- //Dispose handle
- Handle.Dispose();
- }
/// <summary>
/// Resets the request accumulator and writes the initial message id
@@ -91,7 +106,7 @@ namespace VNLib.Net.Messaging.FBM.Client
_requestAccumulator.Reset();
//Write message id to accumulator, it should already be reset
- Helpers.WriteMessageid(RequestBuffer, messageId);
+ Helpers.WriteMessageid(_requestAccumulator, messageId);
}
///<inheritdoc/>
@@ -99,24 +114,24 @@ namespace VNLib.Net.Messaging.FBM.Client
Span<char> IFBMHeaderBuffer.GetSpan(int offset, int count)
{
//Get the character span
- Span<char> span = MemoryMarshal.Cast<byte, char>(Handle.Memory.Span);
+ Span<char> span = MemoryMarshal.Cast<byte, char>(Handle.GetSpan());
return span.Slice(offset, count);
}
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- Span<char> IFBMHeaderBuffer.GetSpan() => MemoryMarshal.Cast<byte, char>(Handle.Memory.Span);
+ Span<char> IFBMHeaderBuffer.GetSpan() => MemoryMarshal.Cast<byte, char>(Handle.GetSpan());
private sealed class BinaryRequestAccumulator : IDataAccumulator<byte>
{
private readonly int Size;
- private readonly Memory<byte> Buffer;
+ private readonly FBMBuffer Buffer;
- internal BinaryRequestAccumulator(Memory<byte> buffer)
+ internal BinaryRequestAccumulator(FBMBuffer buffer, int size)
{
Buffer = buffer;
- Size = buffer.Length;
+ Size = size;
}
///<inheritdoc/>
@@ -126,52 +141,47 @@ namespace VNLib.Net.Messaging.FBM.Client
public int RemainingSize => Size - AccumulatedSize;
///<inheritdoc/>
- public Span<byte> Remaining => Buffer.Span.Slice(AccumulatedSize, RemainingSize);
+ public Span<byte> Remaining => Buffer.Handle.GetSpan().Slice(AccumulatedSize, RemainingSize);
+
///<inheritdoc/>
- public Span<byte> Accumulated => Buffer.Span[..AccumulatedSize];
+ public Span<byte> Accumulated => Buffer.Handle.GetSpan()[..AccumulatedSize];
/// <summary>
/// Gets the accumulated data as a memory segment
/// </summary>
- public Memory<byte> AccumulatedMemory => Buffer[..AccumulatedSize];
+ public Memory<byte> AccumulatedMemory => Buffer.Handle.GetMemory()[..AccumulatedSize];
/// <summary>
/// Gets the remaining buffer segment as a memory segment
/// </summary>
- public Memory<byte> RemainingMemory => Buffer.Slice(AccumulatedSize, RemainingSize);
+ public Memory<byte> RemainingMemory => Buffer.Handle.GetMemory().Slice(AccumulatedSize, RemainingSize);
///<inheritdoc/>
public void Advance(int count) => AccumulatedSize += count;
+
///<inheritdoc/>
public void Reset() => AccumulatedSize = 0;
}
+ /*
+ * A buffer writer that wraps the request accumulator to allow
+ * a finite amount of data to be written to the accumulator since
+ * it uses a fixed size internal buffer.
+ */
private sealed class BufferWriter : IBufferWriter<byte>
{
private readonly FBMBuffer Buffer;
- public BufferWriter(FBMBuffer buffer)
- {
- Buffer = buffer;
- }
+ public BufferWriter(FBMBuffer buffer) => Buffer = buffer;
- public void Advance(int count)
- {
- //Advance the writer
- Buffer.RequestBuffer.Advance(count);
- }
+ ///<inheritdoc/>
+ public void Advance(int count) => Buffer._requestAccumulator.Advance(count);
- public Memory<byte> GetMemory(int sizeHint = 0)
- {
- //Get the remaining memory segment
- return Buffer._requestAccumulator.RemainingMemory;
- }
+ ///<inheritdoc/>
+ public Memory<byte> GetMemory(int sizeHint = 0) => Buffer._requestAccumulator.RemainingMemory;
- public Span<byte> GetSpan(int sizeHint = 0)
- {
- //Get the remaining data segment
- return Buffer.RequestBuffer.Remaining;
- }
+ ///<inheritdoc/>
+ public Span<byte> GetSpan(int sizeHint = 0) => Buffer._requestAccumulator.Remaining;
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs
index 5184c38..c8319fa 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs
@@ -24,7 +24,6 @@
using System;
using System.IO;
-using System.Buffers;
using System.Threading;
using System.Net.WebSockets;
using System.Threading.Tasks;
@@ -34,9 +33,12 @@ using System.Collections.Concurrent;
using VNLib.Net.Http;
using VNLib.Utils;
using VNLib.Utils.IO;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Memory.Caching;
using VNLib.Utils.Logging;
using VNLib.Utils.Extensions;
-using VNLib.Utils.Memory.Caching;
+
+#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
namespace VNLib.Net.Messaging.FBM.Client
{
@@ -60,6 +62,8 @@ namespace VNLib.Net.Messaging.FBM.Client
/// </summary>
public const string REQ_MAX_MESS_QUERY_ARG = "mx";
+ public const int MAX_STREAM_BUFFER_SIZE = 128 * 1024;
+
/// <summary>
/// Raised when the websocket has been closed because an error occured.
/// You may inspect the event args to determine the cause of the error.
@@ -74,12 +78,14 @@ namespace VNLib.Net.Messaging.FBM.Client
private readonly SemaphoreSlim SendLock;
private readonly ConcurrentDictionary<int, FBMRequest> ActiveRequests;
private readonly ObjectRental<FBMRequest> RequestRental;
+ private readonly FBMClientConfig _config;
+ private readonly byte[] _streamBuffer;
/// <summary>
/// The configuration for the current client
/// </summary>
- public FBMClientConfig Config { get; }
-
+ public ref readonly FBMClientConfig Config => ref _config;
+
/// <summary>
/// A handle that is reset when a connection has been successfully set, and is set
/// when the connection exists
@@ -103,9 +109,13 @@ namespace VNLib.Net.Messaging.FBM.Client
ConnectionStatusHandle = new(true);
ActiveRequests = new(Environment.ProcessorCount, 100);
- Config = config;
+ _config = config;
//Init the new client socket
ClientSocket = new(config.RecvBufferSize, config.RecvBufferSize, config.KeepAliveInterval, config.SubProtocol);
+
+ //Init the stream buffer
+ int maxStrmBufSize = Math.Min(config.MaxMessageSize, MAX_STREAM_BUFFER_SIZE);
+ _streamBuffer = new byte[maxStrmBufSize];
}
private void Debug(string format, params string[] strings)
@@ -127,7 +137,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// Allocates and configures a new <see cref="FBMRequest"/> message object for use within the reusable store
/// </summary>
/// <returns>The configured <see cref="FBMRequest"/></returns>
- protected virtual FBMRequest ReuseableRequestConstructor() => new(Config);
+ protected virtual FBMRequest ReuseableRequestConstructor() => new(in _config);
/// <summary>
/// Asynchronously opens a websocket connection with the specifed remote server
@@ -180,10 +190,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="FBMInvalidRequestException"></exception>
- public Task<FBMResponse> SendAsync(FBMRequest request, CancellationToken cancellationToken = default)
- {
- return SendAsync(request, Config.RequestTimeout, cancellationToken);
- }
+ public Task<FBMResponse> SendAsync(FBMRequest request, CancellationToken cancellationToken = default) => SendAsync(request, _config.RequestTimeout, cancellationToken);
/// <summary>
/// Sends a <see cref="FBMRequest"/> to the connected server
@@ -211,6 +218,8 @@ namespace VNLib.Net.Messaging.FBM.Client
try
{
+ FBMResponse response = new();
+
//Get the request data segment
ReadOnlyMemory<byte> requestData = request.GetRequestData();
@@ -224,22 +233,26 @@ namespace VNLib.Net.Messaging.FBM.Client
}
//wait for the response to be set
- await request.Waiter.WaitAsync(timeout, cancellationToken).ConfigureAwait(true);
+ await request.Waiter.GetTask(timeout, cancellationToken).ConfigureAwait(true);
Debug("Received {size} bytes for message {id}", request.Response?.Length ?? 0, request.MessageId);
- return request.GetResponse();
+ //Get the response data
+ request.GetResponse(ref response);
+
+ return response;
}
catch
{
//Remove the request since packet was never sent
ActiveRequests.Remove(request.MessageId, out _);
-
- //Cleanup waiter
- request.Waiter.OnEndRequest();
-
throw;
}
+ finally
+ {
+ //Always cleanup waiter
+ request.Waiter.OnEndRequest();
+ }
}
/// <summary>
@@ -247,30 +260,28 @@ namespace VNLib.Net.Messaging.FBM.Client
/// </summary>
/// <param name="request">The request message to send to the server</param>
/// <param name="payload">Data to stream to the server</param>
- /// <param name="ct">The content type of the stream of data</param>
+ /// <param name="contentType">The content type of the stream of data</param>
/// <param name="cancellationToken">A token to cancel the operation</param>
/// <returns>A task that resolves when the data is sent and the resonse is received</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="InvalidOperationException"></exception>
- public Task StreamDataAsync(FBMRequest request, Stream payload, ContentType ct, CancellationToken cancellationToken = default)
- {
- return StreamDataAsync(request, payload, ct, Config.RequestTimeout, cancellationToken);
- }
+ public Task<FBMResponse> StreamDataAsync(FBMRequest request, Stream payload, ContentType contentType, CancellationToken cancellationToken = default)
+ => StreamDataAsync(request, payload, contentType, _config.RequestTimeout, cancellationToken);
/// <summary>
/// Streams arbitrary binary data to the server with the initial request message
/// </summary>
/// <param name="request">The request message to send to the server</param>
/// <param name="payload">Data to stream to the server</param>
- /// <param name="ct">The content type of the stream of data</param>
+ /// <param name="contentType">The content type of the stream of data</param>
/// <param name="cancellationToken">A token to cancel the operation</param>
/// <param name="timeout">A maxium wait timeout period. If -1 or 0 the timeout is disabled</param>
/// <returns>A task that resolves when the data is sent and the resonse is received</returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="InvalidOperationException"></exception>
- public async Task StreamDataAsync(FBMRequest request, Stream payload, ContentType ct, TimeSpan timeout, CancellationToken cancellationToken = default)
+ public async Task<FBMResponse> StreamDataAsync(FBMRequest request, Stream payload, ContentType contentType, TimeSpan timeout, CancellationToken cancellationToken = default)
{
Check();
@@ -282,19 +293,15 @@ namespace VNLib.Net.Messaging.FBM.Client
try
{
+ FBMResponse response = new();
+
//Get the request data segment
ReadOnlyMemory<byte> requestData = request.GetRequestData();
Debug("Streaming {bytes} with id {id}", requestData.Length, request.MessageId);
- //Write an empty body in the request
- request.WriteBody(ReadOnlySpan<byte>.Empty, ct);
-
- //Calc buffer size
- int bufSize = (int)Math.Clamp(payload.Length, Config.MessageBufferSize, Config.MaxMessageSize);
-
- //Alloc a streaming buffer
- using IMemoryOwner<byte> buffer = Config.BufferHeap.DirectAlloc<byte>(bufSize);
+ //Write an empty body in the request so a content type header is writen
+ request.WriteBody(ReadOnlySpan<byte>.Empty, contentType);
//Wait for send-lock
using (SemSlimReleaser releaser = await SendLock.GetReleaserAsync(cancellationToken))
@@ -306,7 +313,7 @@ namespace VNLib.Net.Messaging.FBM.Client
do
{
//Read data
- int read = await payload.ReadAsync(buffer.Memory, cancellationToken);
+ int read = await payload.ReadAsync(_streamBuffer, cancellationToken);
if (read == 0)
{
@@ -315,29 +322,32 @@ namespace VNLib.Net.Messaging.FBM.Client
}
//write message to socket, if the read data was smaller than the buffer, we can send the last packet
- await ClientSocket.SendAsync(buffer.Memory[..read], WebSocketMessageType.Binary, read < bufSize, cancellationToken);
+ await ClientSocket.SendAsync(_streamBuffer[..read], WebSocketMessageType.Binary, read < _streamBuffer.Length, cancellationToken);
} while (true);
}
//wait for the server to respond
- await request.Waiter.WaitAsync(timeout, cancellationToken).ConfigureAwait(true);
+ await request.Waiter.GetTask(timeout, cancellationToken).ConfigureAwait(true);
Debug("Response recieved {size} bytes for message {id}", request.Response?.Length ?? 0, request.MessageId);
+
+ request.GetResponse(ref response);
+ return response;
}
catch
{
//Remove the request since packet was never sent or cancelled
_ = ActiveRequests.TryRemove(request.MessageId, out _);
-
- //Cleanup request waiter
- request.Waiter.OnEndRequest();
-
throw;
}
+ finally
+ {
+ //Always cleanup waiter
+ request.Waiter.OnEndRequest();
+ }
}
-
/// <summary>
/// Closes the underlying <see cref="WebSocket"/> and cancels all pending operations
/// </summary>
@@ -399,14 +409,20 @@ namespace VNLib.Net.Messaging.FBM.Client
Debug("Begining receive loop");
//Alloc recv buffer
- IMemoryOwner<byte> recvBuffer = Config.BufferHeap.DirectAlloc<byte>(Config.RecvBufferSize);
+ IFBMMemoryHandle recvBuffer = _config.MemoryManager.InitHandle();
+ _config.MemoryManager.AllocBuffer(recvBuffer, _config.RecvBufferSize);
try
{
+ if(!_config.MemoryManager.TryGetHeap(out IUnmangedHeap? heap))
+ {
+ throw new NotSupportedException("The memory manager must support using IUnmanagedHeaps");
+ }
+
//Recv event loop
while (true)
{
//Listen for incoming packets with the intial data buffer
- ValueWebSocketReceiveResult result = await socket.ReceiveAsync(recvBuffer.Memory, CancellationToken.None);
+ ValueWebSocketReceiveResult result = await socket.ReceiveAsync(recvBuffer.GetMemory(), CancellationToken.None);
//If the message is a close message, its time to exit
if (result.MessageType == WebSocketMessageType.Close)
@@ -422,19 +438,19 @@ namespace VNLib.Net.Messaging.FBM.Client
}
//Alloc data buffer and write initial data
- VnMemoryStream responseBuffer = new(Config.BufferHeap);
+ VnMemoryStream responseBuffer = new(heap);
//Copy initial data
- responseBuffer.Write(recvBuffer.Memory.Span[..result.Count]);
+ responseBuffer.Write(recvBuffer.GetSpan()[..result.Count]);
//Receive packets until the EOF is reached
while (!result.EndOfMessage)
{
//recive more data
- result = await socket.ReceiveAsync(recvBuffer.Memory, CancellationToken.None);
+ result = await socket.ReceiveAsync(recvBuffer.GetMemory(), CancellationToken.None);
//Make sure the buffer is not too large
- if ((responseBuffer.Length + result.Count) > Config.MaxMessageSize)
+ if ((responseBuffer.Length + result.Count) > _config.MaxMessageSize)
{
//Dispose the buffer before exiting
responseBuffer.Dispose();
@@ -443,7 +459,7 @@ namespace VNLib.Net.Messaging.FBM.Client
}
//Copy continuous data
- responseBuffer.Write(recvBuffer.Memory.Span[..result.Count]);
+ responseBuffer.Write(recvBuffer.GetSpan()[..result.Count]);
}
//Reset the buffer stream position
@@ -472,7 +488,7 @@ namespace VNLib.Net.Messaging.FBM.Client
finally
{
//Dispose the recv buffer
- recvBuffer.Dispose();
+ _config.MemoryManager.FreeBuffer(recvBuffer);
//Cleanup the socket when exiting
ClientSocket.Cleanup();
//Set status handle as unset
@@ -522,7 +538,12 @@ namespace VNLib.Net.Messaging.FBM.Client
if (ActiveRequests.TryRemove(messageId, out FBMRequest? request))
{
//Set the new response message
- request.Waiter.Complete(responseMessage);
+ if (!request.Waiter.Complete(responseMessage))
+ {
+ //Falied to complete, dispose the message data
+ responseMessage.Dispose();
+ Debug("Failed to transition waiting request {id}. Message was dropped", messageId, 0);
+ }
}
else
{
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs b/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs
index 30e9a95..735a0a8 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs
@@ -25,7 +25,6 @@
using System;
using System.Text;
-using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
namespace VNLib.Net.Messaging.FBM.Client
@@ -59,7 +58,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <summary>
/// The heap to allocate internal (and message) buffers from
/// </summary>
- public readonly IUnmangedHeap BufferHeap { get; init; }
+ public readonly IFBMMemoryManager MemoryManager { get; init; }
/// <summary>
/// The websocket keepalive interval to use (leaving this property default disables keepalives)
/// </summary>
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs
index c4fb493..418a9ec 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs
@@ -26,6 +26,7 @@ using System;
using System.Text;
using System.Buffers;
using System.Threading;
+using System.Diagnostics;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -37,7 +38,6 @@ using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Utils.Memory.Caching;
-
namespace VNLib.Net.Messaging.FBM.Client
{
/// <summary>
@@ -97,29 +97,29 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <param name="messageId">The custom message id</param>
/// <param name="config">The fbm client config storing required config variables</param>
public FBMRequest(int messageId, in FBMClientConfig config)
- :this(messageId, config.BufferHeap, config.MessageBufferSize, config.HeaderEncoding)
+ :this(messageId, config.MemoryManager, config.MessageBufferSize, config.HeaderEncoding)
{ }
/// <summary>
/// Initializes a new <see cref="FBMRequest"/> with the sepcified message buffer size and a custom MessageId
/// </summary>
/// <param name="messageId">The custom message id</param>
- /// <param name="heap">The heap to allocate the internal buffer from</param>
+ /// <param name="manager">The memory manager used to allocate the internal buffers</param>
/// <param name="bufferSize">The size of the internal buffer</param>
/// <param name="headerEncoding">The encoding instance used for header character encoding</param>
- public FBMRequest(int messageId, IUnmangedHeap heap, int bufferSize, Encoding headerEncoding)
+ public FBMRequest(int messageId, IFBMMemoryManager manager, int bufferSize, Encoding headerEncoding)
{
MessageId = messageId;
- HeaderEncoding = headerEncoding;
+ HeaderEncoding = headerEncoding ?? throw new ArgumentNullException(nameof(headerEncoding));
+ _ = manager ?? throw new ArgumentNullException(nameof(manager));
//Configure waiter
Waiter = new FBMMessageWaiter(this);
-
- //Alloc the buffer as a memory owner so a memory buffer can be used
- IMemoryOwner<byte> HeapBuffer = heap.DirectAlloc<byte>(bufferSize);
- Buffer = new(HeapBuffer);
+
+ Buffer = new(manager, bufferSize);
//Prepare the message incase the request is fresh
+ Buffer.Prepare();
Reset();
}
@@ -152,19 +152,13 @@ namespace VNLib.Net.Messaging.FBM.Client
/// The request message packet, this may cause side effects
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ReadOnlyMemory<byte> GetRequestData()
- {
- return Buffer.RequestData;
- }
+ public ReadOnlyMemory<byte> GetRequestData() => Buffer.RequestData;
/// <summary>
/// Resets the internal buffer and allows for writing a new message with
/// the same message-id
/// </summary>
- public void Reset()
- {
- Buffer.Reset(MessageId);
- }
+ public void Reset() => Buffer.Reset(MessageId);
///<inheritdoc/>
protected override void Free()
@@ -175,7 +169,12 @@ namespace VNLib.Net.Messaging.FBM.Client
(Waiter as FBMMessageWaiter)!.Dispose();
}
- void IReusable.Prepare() => Reset();
+ void IReusable.Prepare()
+ {
+ //MUST BE CALLED FIRST!
+ Buffer.Prepare();
+ Reset();
+ }
bool IReusable.Release()
{
@@ -186,6 +185,9 @@ namespace VNLib.Net.Messaging.FBM.Client
Response?.Dispose();
Response = null;
+ //Free buffer
+ Buffer.Release();
+
return true;
}
@@ -195,7 +197,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// Gets the response of the sent message
/// </summary>
/// <returns>The response message for the current request</returns>
- internal FBMResponse GetResponse()
+ internal void GetResponse(ref FBMResponse response)
{
if (Response != null)
{
@@ -215,11 +217,7 @@ namespace VNLib.Net.Messaging.FBM.Client
HeaderParseError statusFlags = Helpers.ParseHeaders(Response, Buffer, ResponseHeaderList, HeaderEncoding);
//return response structure
- return new(Response, statusFlags, ResponseHeaderList, OnResponseDisposed);
- }
- else
- {
- return new();
+ response = new(Response, statusFlags, ResponseHeaderList, OnResponseDisposed);
}
}
@@ -272,12 +270,13 @@ namespace VNLib.Net.Messaging.FBM.Client
#endregion
#region waiter
- private sealed class FBMMessageWaiter : IFBMMessageWaiter, IDisposable
+ private sealed class FBMMessageWaiter : IFBMMessageWaiter, IDisposable, IThreadPoolWorkItem
{
private readonly Timer _timer;
private readonly FBMRequest _request;
private TaskCompletionSource? _tcs;
+ private CancellationTokenRegistration _token;
public FBMMessageWaiter(FBMRequest request)
{
@@ -298,79 +297,89 @@ namespace VNLib.Net.Messaging.FBM.Client
public void OnEndRequest()
{
//Cleanup tcs ref
- _ = Interlocked.Exchange(ref _tcs, null);
+ _tcs = null;
- //Stop timer if set
+ //Always stop timer if set
_timer.Stop();
+
+ //Cleanup cancellation token
+ _token.Dispose();
}
///<inheritdoc/>
- public void Complete(VnMemoryStream ms)
+ public bool Complete(VnMemoryStream ms)
{
- //Read the current state of the tcs
TaskCompletionSource? tcs = _tcs;
- if (tcs == null)
+ //Work is done/cancelled
+ if (tcs != null && tcs.Task.IsCompleted)
{
- //Work is done/cancelled, dispose the ms and leave
- ms.Dispose();
+ return false;
}
//Store response
_request.Response = ms;
- //Transition to completed state in background thread
- static void OnTpCallback(object? state)
- {
- _ = (state as TaskCompletionSource)!.TrySetResult();
- }
-
/*
* The calling thread may be a TP thread proccessing an async event loop.
* We do not want to block this worker thread.
*/
- ThreadPool.UnsafeQueueUserWorkItem(OnTpCallback, tcs);
+ return ThreadPool.UnsafeQueueUserWorkItem(this, true);
}
+ /*
+ * Called when scheduled on the TP thread pool
+ */
///<inheritdoc/>
- public async Task WaitAsync(TimeSpan timeout, CancellationToken cancellation)
+ public void Execute() => _tcs?.TrySetResult();
+
+
+ ///<inheritdoc/>
+ public Task GetTask(TimeSpan timeout, CancellationToken cancellation)
{
- if (timeout.Ticks > 0)
- {
- //Restart timer if timeout is configured
- _timer.Restart(timeout);
- }
+ TaskCompletionSource? tcs = _tcs;
- //Confim the token may be cancelled
- if (cancellation.CanBeCanceled)
- {
- //Register cancellation
- using CancellationTokenRegistration reg = cancellation.Register(OnCancelled, this, false);
+ Debug.Assert(tcs != null, "A call to GetTask was made outside of the request flow, the TaskCompletionSource was null");
- //await task that may be canclled
- await _tcs.Task.ConfigureAwait(false);
- }
- else
+ /*
+ * Get task will only be called after the message has been sent.
+ * The Complete method may have already scheduled a completion by
+ * the time this method is called, so we may avoid setting up the
+ * timer and cancellation if possible. Also since this mthod is
+ * called from the request side, we know the tcs cannot be null
+ */
+
+ if (!tcs.Task.IsCompleted)
{
- //await the task directly
- await _tcs.Task.ConfigureAwait(false);
+ if (timeout.Ticks > 0)
+ {
+ //Restart timer if timeout is configured
+ _timer.Restart(timeout);
+ }
+
+ if (cancellation.CanBeCanceled)
+ {
+ //Register cancellation
+ _token = cancellation.Register(OnCancelled, this);
+ }
}
+
+ return tcs.Task;
}
///<inheritdoc/>
public void ManualCancellation() => OnCancelled(this);
- private void OnCancelled(object? state)
- {
- //Set cancelled state if exists
- _ = _tcs?.TrySetCanceled();
- }
+ //Set cancelled state if exists, the task may have already completed
+ private void OnCancelled(object? state) => _tcs?.TrySetCanceled();
///<inheritdoc/>
public void Dispose()
{
_timer.Dispose();
+ _token.Dispose();
}
+
}
#endregion
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs b/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs
index 6f8fec4..f1148f1 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -90,17 +90,16 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <summary>
/// Releases any resources associated with the response message
/// </summary>
- public void Dispose() => _onDispose?.Invoke();
+ public readonly void Dispose() => _onDispose?.Invoke();
///<inheritdoc/>
public override bool Equals(object? obj) => obj is FBMResponse response && Equals(response);
///<inheritdoc/>
+ public bool Equals(FBMResponse other) => IsSet && other.IsSet && ReferenceEquals(MessagePacket, other.MessagePacket);
+ ///<inheritdoc/>
public override int GetHashCode() => IsSet ? MessagePacket!.GetHashCode() : 0;
///<inheritdoc/>
public static bool operator ==(FBMResponse left, FBMResponse right) => left.Equals(right);
///<inheritdoc/>
public static bool operator !=(FBMResponse left, FBMResponse right) => !(left == right);
- ///<inheritdoc/>
- public bool Equals(FBMResponse other) => IsSet && other.IsSet && MessagePacket == other.MessagePacket;
-
}
}
diff --git a/lib/Net.Messaging.FBM/src/Client/IFBMMessageWaiter.cs b/lib/Net.Messaging.FBM/src/Client/IFBMMessageWaiter.cs
index 5000711..cc8e1c4 100644
--- a/lib/Net.Messaging.FBM/src/Client/IFBMMessageWaiter.cs
+++ b/lib/Net.Messaging.FBM/src/Client/IFBMMessageWaiter.cs
@@ -48,9 +48,9 @@ namespace VNLib.Net.Messaging.FBM.Client
/// or a timeout
/// </summary>
/// <param name="timeout">The maxium time to wait for the server to respond (may be default/0)</param>
- /// <param name="cancellation">The cancellation token to observe</param>
+ /// <param name="cancellation">A token to cancel the wait task</param>
/// <returns>A task that completes when the server responds</returns>
- Task WaitAsync(TimeSpan timeout, CancellationToken cancellation);
+ Task GetTask(TimeSpan timeout, CancellationToken cancellation);
/// <summary>
/// Called by the client to cleanup the waiter when the request is completed
@@ -63,7 +63,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// Set by the client when the response has been successfully received by the client
/// </summary>
/// <param name="ms">The response data to pass to the response</param>
- void Complete(VnMemoryStream ms);
+ bool Complete(VnMemoryStream ms);
/// <summary>
/// Called to invoke a manual cancellation of a request waiter. This method should
diff --git a/lib/Net.Messaging.FBM/src/Client/ManagedClientWebSocket.cs b/lib/Net.Messaging.FBM/src/Client/ManagedClientWebSocket.cs
index d0352d3..fc2e417 100644
--- a/lib/Net.Messaging.FBM/src/Client/ManagedClientWebSocket.cs
+++ b/lib/Net.Messaging.FBM/src/Client/ManagedClientWebSocket.cs
@@ -43,7 +43,7 @@ namespace VNLib.Net.Messaging.FBM.Client
private readonly int TxBufferSize;
private readonly int RxBufferSize;
private readonly TimeSpan KeepAliveInterval;
- private readonly VnTempBuffer<byte> _dataBuffer;
+ private readonly ArrayPoolBuffer<byte> _dataBuffer;
private readonly string? _subProtocol;
/// <summary>
@@ -95,7 +95,7 @@ namespace VNLib.Net.Messaging.FBM.Client
try
{
//Set buffer
- _socket.Options.SetBuffer(RxBufferSize, TxBufferSize, _dataBuffer);
+ _socket.Options.SetBuffer(RxBufferSize, TxBufferSize, _dataBuffer.AsArraySegment());
//Set remaining stored options
_socket.Options.ClientCertificates = Certificates;
_socket.Options.KeepAliveInterval = KeepAliveInterval;
diff --git a/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs
index d1f4f1c..180ce7d 100644
--- a/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs
+++ b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs
@@ -87,6 +87,7 @@ namespace VNLib.Net.Messaging.FBM
///<inheritdoc/>
public static bool operator ==(FBMMessageHeader left, FBMMessageHeader right) => left.Equals(right);
+
///<inheritdoc/>
public static bool operator !=(FBMMessageHeader left, FBMMessageHeader right) => !(left == right);
diff --git a/lib/Net.Messaging.FBM/src/FallbackFBMMemoryManager.cs b/lib/Net.Messaging.FBM/src/FallbackFBMMemoryManager.cs
new file mode 100644
index 0000000..260cbd6
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/FallbackFBMMemoryManager.cs
@@ -0,0 +1,140 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: FallbackFBMMemoryManager.cs
+*
+* FallbackFBMMemoryManager.cs is part of VNLib.Net.Messaging.FBM which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Messaging.FBM 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+namespace VNLib.Net.Messaging.FBM
+{
+ /// <summary>
+ /// A default/fallback implementation of a <see cref="IFBMMemoryManager"/> that
+ /// uses an <see cref="IUnmangedHeap"/> to allocate buffers from
+ /// </summary>
+ public sealed class FallbackFBMMemoryManager : IFBMMemoryManager
+ {
+ private readonly IUnmangedHeap _heap;
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="FallbackFBMMemoryManager"/> allocationg
+ /// memory from the specified <see cref="IUnmangedHeap"/>
+ /// </summary>
+ /// <param name="heap">The heap to allocate memory from</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public FallbackFBMMemoryManager(IUnmangedHeap heap) => _heap = heap ?? throw new ArgumentNullException(nameof(heap));
+
+ ///<inheritdoc/>
+ public void AllocBuffer(IFBMSpanOnlyMemoryHandle state, int size)
+ {
+ _ = state ?? throw new ArgumentNullException(nameof(state));
+ (state as IFBMBufferHolder)!.AllocBuffer(size);
+ }
+
+ ///<inheritdoc/>
+ public void FreeBuffer(IFBMSpanOnlyMemoryHandle state)
+ {
+ _ = state ?? throw new ArgumentNullException(nameof(state));
+ (state as IFBMBufferHolder)!.FreeBuffer();
+ }
+
+ ///<inheritdoc/>
+ public IFBMMemoryHandle InitHandle() => new FBMMemHandle(_heap);
+
+ ///<inheritdoc/>
+ public IFBMSpanOnlyMemoryHandle InitSpanOnly() => new FBMSpanOnlyMemHandle(_heap);
+
+ ///<inheritdoc/>
+ public bool TryGetHeap([NotNullWhen(true)] out IUnmangedHeap? heap)
+ {
+ heap = _heap;
+ return true;
+ }
+
+ interface IFBMBufferHolder
+ {
+ void AllocBuffer(int size);
+
+ void FreeBuffer();
+ }
+
+ private sealed record class FBMMemHandle(IUnmangedHeap Heap) : IFBMMemoryHandle, IFBMBufferHolder
+ {
+ private MemoryHandle<byte>? _handle;
+ private IMemoryOwner<byte>? _memHandle;
+
+ ///<inheritdoc/>
+ public Memory<byte> GetMemory()
+ {
+ _ = _memHandle ?? throw new InvalidOperationException("Buffer has not allocated");
+ return _memHandle.Memory;
+ }
+
+ ///<inheritdoc/>
+ public Span<byte> GetSpan()
+ {
+ _ = _handle ?? throw new InvalidOperationException("Buffer has not allocated");
+ return _handle.Span;
+ }
+
+ ///<inheritdoc/>
+ public void AllocBuffer(int size)
+ {
+ //Alloc buffer and memory manager to wrap it
+ _handle = Heap.Alloc<byte>(size, false);
+ _memHandle = _handle.ToMemoryManager(false);
+ }
+
+ ///<inheritdoc/>
+ public void FreeBuffer()
+ {
+ _handle?.Dispose();
+ _memHandle?.Dispose();
+
+ _handle = null;
+ _memHandle = null;
+ }
+ }
+
+ private sealed record class FBMSpanOnlyMemHandle(IUnmangedHeap Heap) : IFBMSpanOnlyMemoryHandle, IFBMBufferHolder
+ {
+ private MemoryHandle<byte>? _handle;
+
+ ///<inheritdoc/>
+ public void AllocBuffer(int size) => _handle = Heap.Alloc<byte>(size, false);
+
+ ///<inheritdoc/>
+ public void FreeBuffer() => _handle?.Dispose();
+
+ ///<inheritdoc/>
+ public Span<byte> GetSpan()
+ {
+ _ = _handle ?? throw new InvalidOperationException("Buffer has not allocated");
+ return _handle.Span;
+ }
+ }
+ }
+}
diff --git a/lib/Net.Messaging.FBM/src/IFBMMemoryHandle.cs b/lib/Net.Messaging.FBM/src/IFBMMemoryHandle.cs
new file mode 100644
index 0000000..97a41b4
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/IFBMMemoryHandle.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: IFBMMemoryHandle.cs
+*
+* IFBMMemoryHandle.cs is part of VNLib.Net.Messaging.FBM which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Messaging.FBM 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+
+namespace VNLib.Net.Messaging.FBM
+{
+ /// <summary>
+ /// Represents a configurable handle to a memory block
+ /// </summary>
+ public interface IFBMMemoryHandle : IFBMSpanOnlyMemoryHandle
+ {
+ /// <summary>
+ /// Gets the block as a <see cref="Memory{T}"/>
+ /// structure
+ /// </summary>
+ /// <returns>The memory structure wrapping the underlying memory block</returns>
+ Memory<byte> GetMemory();
+ }
+
+}
diff --git a/lib/Net.Messaging.FBM/src/IFBMMemoryManager.cs b/lib/Net.Messaging.FBM/src/IFBMMemoryManager.cs
new file mode 100644
index 0000000..9342993
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/IFBMMemoryManager.cs
@@ -0,0 +1,71 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: IFBMMemoryManager.cs
+*
+* IFBMMemoryManager.cs is part of VNLib.Net.Messaging.FBM which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Messaging.FBM 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Net.Messaging.FBM
+{
+ /// <summary>
+ /// Manages memory blocks required by the FBM server messages
+ /// </summary>
+ public interface IFBMMemoryManager
+ {
+ /// <summary>
+ /// Initializes a new <see cref="IFBMMemoryHandle"/>
+ /// </summary>
+ /// <returns>The initialized handle</returns>
+ IFBMMemoryHandle InitHandle();
+
+ /// <summary>
+ /// Initializes a new <see cref="IFBMSpanOnlyMemoryHandle"/>
+ /// </summary>
+ /// <returns>The initialized handle</returns>
+ IFBMSpanOnlyMemoryHandle InitSpanOnly();
+
+ /// <summary>
+ /// Allocates the <see cref="IFBMMemoryHandle"/> internal buffer
+ /// for use
+ /// </summary>
+ /// <param name="state">The memory handle to allocate the buffer for</param>
+ /// <param name="size">The size of the buffer required</param>
+ void AllocBuffer(IFBMSpanOnlyMemoryHandle state, int size);
+
+ /// <summary>
+ /// Frees the <see cref="IFBMSpanOnlyMemoryHandle"/> internal buffer
+ /// </summary>
+ /// <param name="state">The buffer handle holding the memory to free</param>
+ void FreeBuffer(IFBMSpanOnlyMemoryHandle state);
+
+ /// <summary>
+ /// Tries to get the internal <see cref="IUnmangedHeap"/> to allocate internal
+ /// buffers from
+ /// </summary>
+ /// <param name="heap">The internal heap</param>
+ /// <returns>A value that indicates if a backing heap is supported and can be recovered</returns>
+ bool TryGetHeap([NotNullWhen(true)]out IUnmangedHeap? heap);
+ }
+
+}
diff --git a/lib/Net.Messaging.FBM/src/Client/IFBMMessage.cs b/lib/Net.Messaging.FBM/src/IFBMMessage.cs
index 18f19ec..dba605d 100644
--- a/lib/Net.Messaging.FBM/src/Client/IFBMMessage.cs
+++ b/lib/Net.Messaging.FBM/src/IFBMMessage.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -26,7 +26,7 @@ using System;
using VNLib.Net.Http;
-namespace VNLib.Net.Messaging.FBM.Client
+namespace VNLib.Net.Messaging.FBM
{
/// <summary>
/// Represents basic Fixed Buffer Message protocol operations
diff --git a/lib/Net.Messaging.FBM/src/IFBMSpanOnlyMemoryHandle.cs b/lib/Net.Messaging.FBM/src/IFBMSpanOnlyMemoryHandle.cs
new file mode 100644
index 0000000..0078357
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/IFBMSpanOnlyMemoryHandle.cs
@@ -0,0 +1,42 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: IFBMSpanOnlyMemoryHandle.cs
+*
+* IFBMSpanOnlyMemoryHandle.cs is part of VNLib.Net.Messaging.FBM which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Messaging.FBM 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+
+namespace VNLib.Net.Messaging.FBM
+{
+ /// <summary>
+ /// Represents a configurable handle to a memory block
+ /// </summary>
+ public interface IFBMSpanOnlyMemoryHandle
+ {
+ /// <summary>
+ /// Gets the block as a <see cref="Span{T}"/>
+ /// </summary>
+ /// <returns>The memory block as a span</returns>
+ Span<byte> GetSpan();
+ }
+
+}
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMContext.cs b/lib/Net.Messaging.FBM/src/Server/FBMContext.cs
index 6d5f3bd..f2a2fea 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMContext.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMContext.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -55,10 +55,11 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <param name="requestHeaderBufferSize">The size in characters of the request header buffer</param>
/// <param name="responseBufferSize">The size in characters of the response header buffer</param>
/// <param name="headerEncoding">The message header encoding instance</param>
- public FBMContext(int requestHeaderBufferSize, int responseBufferSize, Encoding headerEncoding)
+ /// <param name="manager">The context memory manager</param>
+ public FBMContext(int requestHeaderBufferSize, int responseBufferSize, Encoding headerEncoding, IFBMMemoryManager manager)
{
- _request = Request = new(requestHeaderBufferSize);
- _response = Response = new(responseBufferSize, headerEncoding);
+ _request = Request = new(requestHeaderBufferSize, manager);
+ _response = Response = new(responseBufferSize, headerEncoding, manager);
_headerEncoding = headerEncoding;
}
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs
index 46ee160..30fa1ac 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -24,7 +24,6 @@
using System;
using System.IO;
-using System.Buffers;
using System.Threading;
using System.Net.WebSockets;
using System.Threading.Tasks;
@@ -32,7 +31,6 @@ using System.Threading.Tasks;
using VNLib.Utils.IO;
using VNLib.Utils.Async;
using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
using VNLib.Utils.Memory.Caching;
using VNLib.Plugins.Essentials;
@@ -40,16 +38,6 @@ namespace VNLib.Net.Messaging.FBM.Server
{
/// <summary>
- /// Method delegate for processing FBM messages from an <see cref="FBMListener"/>
- /// when messages are received
- /// </summary>
- /// <param name="context">The message/connection context</param>
- /// <param name="userState">The state parameter passed on client connected</param>
- /// <param name="cancellationToken">A token that reflects the state of the listener</param>
- /// <returns>A <see cref="Task"/> that resolves when processing is complete</returns>
- public delegate Task RequestHandler(FBMContext context, object? userState, CancellationToken cancellationToken);
-
- /// <summary>
/// A FBM protocol listener. Listens for messages on a <see cref="WebSocketSession"/>
/// and raises events on requests.
/// </summary>
@@ -58,22 +46,16 @@ namespace VNLib.Net.Messaging.FBM.Server
public const int SEND_SEMAPHORE_TIMEOUT_MS = 10 * 1000;
- private readonly IUnmangedHeap Heap;
-
- /// <summary>
- /// Raised when a response processing error occured
- /// </summary>
- public event EventHandler<Exception>? OnProcessError;
+ private readonly IFBMMemoryManager MemoryManger;
/// <summary>
/// Creates a new <see cref="FBMListener"/> instance ready for
/// processing connections
/// </summary>
/// <param name="heap">The heap to alloc buffers from</param>
- public FBMListener(IUnmangedHeap heap)
- {
- Heap = heap;
- }
+ /// <exception cref="ArgumentNullException"></exception>
+ public FBMListener(IFBMMemoryManager heap) => MemoryManger = heap ?? throw new ArgumentNullException(nameof(heap));
+
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
/// <summary>
@@ -83,31 +65,38 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <param name="wss">The <see cref="WebSocketSession"/> to receive messages on</param>
/// <param name="handler">The callback method to handle incoming requests</param>
/// <param name="args">The arguments used to configured this listening session</param>
- /// <param name="userState">A state parameter</param>
/// <returns>A <see cref="Task"/> that completes when the connection closes</returns>
- public async Task ListenAsync(WebSocketSession wss, RequestHandler handler, FBMListenerSessionParams args, object? userState)
+ public async Task ListenAsync(WebSocketSession wss, IFBMServerMessageHandler handler, FBMListenerSessionParams args)
{
_ = wss ?? throw new ArgumentNullException(nameof(wss));
_ = handler ?? throw new ArgumentNullException(nameof(handler));
- ListeningSession session = new(wss, handler, in args, userState);
-
- //Alloc a recieve buffer
- using IMemoryOwner<byte> recvBuffer = Heap.DirectAlloc<byte>(args.RecvBufferSize);
+ ListeningSession session = new(wss, handler, in args, MemoryManger);
//Init new queue for dispatching work
AsyncQueue<VnMemoryStream> workQueue = new(true, true);
//Start a task to process the queue
Task queueWorker = QueueWorkerDoWork(workQueue, session);
-
+
+ //Alloc buffer
+ IFBMMemoryHandle memHandle = MemoryManger.InitHandle();
+ MemoryManger.AllocBuffer(memHandle, args.RecvBufferSize);
+
try
{
+ if(!MemoryManger.TryGetHeap(out IUnmangedHeap? heap))
+ {
+ throw new NotSupportedException("The memory manager must export an unmanaged heap");
+ }
+
+ Memory<byte> recvBuffer = memHandle.GetMemory();
+
//Listen for incoming messages
while (true)
{
//Receive a message
- ValueWebSocketReceiveResult result = await wss.ReceiveAsync(recvBuffer.Memory);
+ ValueWebSocketReceiveResult result = await wss.ReceiveAsync(recvBuffer);
//If a close message has been received, we can gracefully exit
if (result.MessageType == WebSocketMessageType.Close)
{
@@ -118,13 +107,13 @@ namespace VNLib.Net.Messaging.FBM.Server
}
//create buffer for storing data, pre alloc with initial data
- VnMemoryStream request = new(Heap, recvBuffer.Memory[..result.Count]);
+ VnMemoryStream request = new(heap, recvBuffer[..result.Count]);
//Streaming read
while (!result.EndOfMessage)
{
//Read more data
- result = await wss.ReceiveAsync(recvBuffer.Memory);
+ result = await wss.ReceiveAsync(recvBuffer);
//Make sure the request is small enough to buffer
if (request.Length + result.Count > args.MaxMessageSize)
{
@@ -135,8 +124,9 @@ namespace VNLib.Net.Messaging.FBM.Server
//break listen loop
goto Exit;
}
+
//write to buffer
- request.Write(recvBuffer.Memory.Span[..result.Count]);
+ request.Write(memHandle.GetSpan()[..result.Count]);
}
//Make sure data is available
if (request.Length == 0)
@@ -195,14 +185,19 @@ namespace VNLib.Net.Messaging.FBM.Server
if ((context.Request.ParseStatus & HeaderParseError.InvalidId) > 0)
{
- OnProcessError?.Invoke(this, new FBMException($"Invalid messageid {context.Request.MessageId}, message length {data.Length}"));
- return;
+ Exception cause = new FBMException($"Invalid messageid {context.Request.MessageId}, message length {data.Length}");
+ _ = session.Handler.OnInvalidMessage(context, cause);
+ return; //Cannot continue on invalid message id
}
//Check parse status flags
if ((context.Request.ParseStatus & HeaderParseError.HeaderOutOfMem) > 0)
{
- OnProcessError?.Invoke(this, new FBMException("Packet received with not enough space to store headers"));
+ Exception cause = new FBMException("Packet received with not enough space to store headers");
+ if(!session.Handler.OnInvalidMessage(context, cause))
+ {
+ return;
+ }
}
//Determine if request is an out-of-band message
else if (context.Request.MessageId == Helpers.CONTROL_FRAME_MID)
@@ -213,18 +208,15 @@ namespace VNLib.Net.Messaging.FBM.Server
else
{
//Invoke normal message handler
- await session.OnRecieved.Invoke(context, session.UserState, session.CancellationToken);
+ await session.Handler.HandleMessage(context, session.CancellationToken);
}
- //Get response data
-
- await using IAsyncMessageReader messageEnumerator = await context.Response.GetResponseDataAsync(session.CancellationToken);
-
+ //Get response data reader
+ await using IAsyncMessageReader messageEnumerator = context.Response.GetResponseData();
//Load inital segment
if (await messageEnumerator.MoveNextAsync() && !session.CancellationToken.IsCancellationRequested)
- {
- ValueTask sendTask;
+ {
//Syncrhonize access to send data because we may need to stream data to the client
await session.ResponseLock.WaitAsync(SEND_SEMAPHORE_TIMEOUT_MS);
@@ -233,10 +225,8 @@ namespace VNLib.Net.Messaging.FBM.Server
{
do
{
- bool eof = !messageEnumerator.DataRemaining;
-
- //Send first segment
- sendTask = session.Socket.SendAsync(messageEnumerator.Current, WebSocketMessageType.Binary, eof);
+ //Send current segment
+ await session.Socket.SendAsync(messageEnumerator.Current, WebSocketMessageType.Binary, !messageEnumerator.DataRemaining);
/*
* WARNING!
@@ -250,9 +240,6 @@ namespace VNLib.Net.Messaging.FBM.Server
{
break;
}
-
- //Await previous send
- await sendTask;
} while (true);
}
@@ -261,15 +248,13 @@ namespace VNLib.Net.Messaging.FBM.Server
//release semaphore
session.ResponseLock.Release();
}
-
- await sendTask;
}
//No data to send
}
catch (Exception ex)
{
- OnProcessError?.Invoke(this, ex);
+ session.Handler.OnProcessError(ex);
}
finally
{
@@ -295,25 +280,23 @@ namespace VNLib.Net.Messaging.FBM.Server
private readonly CancellationTokenSource Cancellation;
private readonly CancellationTokenRegistration Registration;
private readonly FBMListenerSessionParams Params;
+ private readonly IFBMMemoryManager MemManager;
- public readonly object? UserState;
-
public readonly SemaphoreSlim ResponseLock;
public readonly WebSocketSession Socket;
- public readonly RequestHandler OnRecieved;
+ public readonly IFBMServerMessageHandler Handler;
public CancellationToken CancellationToken => Cancellation.Token;
-
- public ListeningSession(WebSocketSession session, RequestHandler onRecieved, in FBMListenerSessionParams args, object? userState)
+ public ListeningSession(WebSocketSession session, IFBMServerMessageHandler handler, in FBMListenerSessionParams args, IFBMMemoryManager memManager)
{
Params = args;
Socket = session;
- UserState = userState;
- OnRecieved = onRecieved;
+ Handler = handler;
+ MemManager = memManager;
//Create cancellation and register for session close
Cancellation = new();
@@ -323,7 +306,7 @@ namespace VNLib.Net.Messaging.FBM.Server
CtxStore = ObjectRental.CreateReusable(ContextCtor);
}
- private FBMContext ContextCtor() => new(Params.MaxHeaderBufferSize, Params.ResponseBufferSize, Params.HeaderEncoding);
+ private FBMContext ContextCtor() => new(Params.MaxHeaderBufferSize, Params.ResponseBufferSize, Params.HeaderEncoding, MemManager);
/// <summary>
/// Cancels any pending opreations relating to the current session
@@ -358,7 +341,6 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <exception cref="ObjectDisposedException"></exception>
public FBMContext RentContext()
{
-
if (Cancellation.IsCancellationRequested)
{
throw new ObjectDisposedException("The instance has been disposed");
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListenerBase.cs b/lib/Net.Messaging.FBM/src/Server/FBMListenerBase.cs
index 3e9fde2..71b1c8f 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMListenerBase.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMListenerBase.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -27,67 +27,29 @@ using System.Threading;
using System.Threading.Tasks;
using VNLib.Utils.Logging;
-using VNLib.Utils.Memory;
using VNLib.Plugins.Essentials;
namespace VNLib.Net.Messaging.FBM.Server
{
+
/// <summary>
/// Provides a simple base class for an <see cref="FBMListener"/>
/// processor
/// </summary>
- public abstract class FBMListenerBase
+ public abstract class FBMListenerBase<T> : IFBMServerErrorHandler
{
/// <summary>
/// The initialzied listener
/// </summary>
- protected FBMListener? Listener { get; private set; }
+ protected abstract FBMListener Listener { get; }
+
/// <summary>
/// A provider to write log information to
/// </summary>
protected abstract ILogProvider Log { get; }
/// <summary>
- /// Initializes the <see cref="FBMListener"/>
- /// </summary>
- /// <param name="heap">The heap to alloc buffers from</param>
- protected void InitListener(IUnmangedHeap heap)
- {
- Listener = new(heap);
- //Attach service handler
- Listener.OnProcessError += Listener_OnProcessError;
- }
-
- /// <summary>
- /// A single event service routine for servicing errors that occur within
- /// the listener loop
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e">The exception that was raised</param>
- protected virtual void Listener_OnProcessError(object? sender, Exception e)
- {
- //Write the error to the log
- Log.Error(e);
- }
-
- private async Task OnReceivedAsync(FBMContext context, object? userState, CancellationToken token)
- {
- try
- {
- await ProcessAsync(context, userState, token);
- }
- catch (OperationCanceledException)
- {
- Log.Debug("Async operation cancelled");
- }
- catch(Exception ex)
- {
- Log.Error(ex);
- }
- }
-
- /// <summary>
/// Begins listening for requests on the current websocket until
/// a close message is received or an error occurs
/// </summary>
@@ -95,10 +57,13 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <param name="args">The arguments used to configured this listening session</param>
/// <param name="userState">A state token to use for processing events for this connection</param>
/// <returns>A <see cref="Task"/> that completes when the connection closes</returns>
- public virtual async Task ListenAsync(WebSocketSession wss, FBMListenerSessionParams args, object? userState)
+ /// <exception cref="InvalidOperationException"></exception>
+ public virtual Task ListenAsync(WebSocketSession wss, T userState, FBMListenerSessionParams args)
{
_ = Listener ?? throw new InvalidOperationException("The listener has not been intialized");
- await Listener.ListenAsync(wss, OnReceivedAsync, args, userState);
+ //Initn new event handler
+ FBMEventHandler handler = new(userState, this);
+ return Listener.ListenAsync(wss, handler, args);
}
/// <summary>
@@ -108,6 +73,30 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <param name="userState">A state token passed on client connected</param>
/// <param name="exitToken">A token that reflects the state of the listener</param>
/// <returns>A task that completes when the message has been serviced</returns>
- protected abstract Task ProcessAsync(FBMContext context, object? userState, CancellationToken exitToken);
+ protected abstract Task ProcessAsync(FBMContext context, T? userState, CancellationToken exitToken);
+
+ ///<inheritdoc/>
+ public virtual bool OnInvalidMessage(FBMContext context, Exception ex)
+ {
+ Log.Error("Invalid message received for session {ses}\n{ex}", context.Request.ConnectionId, ex);
+ //Invalid id should be captured already, so if oom, do not allow, but if a single header is invalid, it will be ignored by default
+ return !context.Request.ParseStatus.HasFlag(HeaderParseError.HeaderOutOfMem);
+ }
+
+ ///<inheritdoc/>
+ public virtual void OnProcessError(Exception ex) => Log.Error(ex);
+
+
+ private sealed record class FBMEventHandler(T State, FBMListenerBase<T> Lb) : IFBMServerMessageHandler
+ {
+ ///<inheritdoc/>
+ public Task HandleMessage(FBMContext context, CancellationToken cancellationToken) => Lb.ProcessAsync(context, State, cancellationToken);
+
+ ///<inheritdoc/>
+ public bool OnInvalidMessage(FBMContext context, Exception ex) => Lb.OnInvalidMessage(context, ex);
+
+ ///<inheritdoc/>
+ public void OnProcessError(Exception ex) => Lb.OnProcessError(ex);
+ }
}
}
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs b/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs
index ccf79db..0b4fa5b 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -30,7 +30,7 @@ namespace VNLib.Net.Messaging.FBM.Server
/// Represents a configuration structure for an <see cref="FBMListener"/>
/// listening session
/// </summary>
- public readonly struct FBMListenerSessionParams
+ public readonly record struct FBMListenerSessionParams
{
/// <summary>
/// The size of the buffer to use while reading data from the websocket
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs b/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
index db0655a..e9ff9f5 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -24,13 +24,12 @@
using System;
using System.Text;
-using System.Buffers;
-using System.Text.Json;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using VNLib.Utils.IO;
-using VNLib.Utils.Extensions;
+using VNLib.Utils.Memory;
using VNLib.Utils.Memory.Caching;
namespace VNLib.Net.Messaging.FBM.Server
@@ -42,18 +41,21 @@ namespace VNLib.Net.Messaging.FBM.Server
{
private readonly List<FBMMessageHeader> _headers;
private readonly int HeaderBufferSize;
+ private readonly IFBMMemoryManager _memoryManager;
+ private readonly IFBMSpanOnlyMemoryHandle _memHandle;
/// <summary>
/// Creates a new resusable <see cref="FBMRequestMessage"/>
/// </summary>
/// <param name="headerBufferSize">The size of the buffer to alloc during initialization</param>
- internal FBMRequestMessage(int headerBufferSize)
+ /// <param name="manager">The memory manager to use for the message</param>
+ internal FBMRequestMessage(int headerBufferSize, IFBMMemoryManager manager)
{
HeaderBufferSize = headerBufferSize;
_headers = new();
+ _memoryManager = manager;
+ _memHandle = _memoryManager.InitSpanOnly();
}
-
- private char[]? _headerBuffer;
/// <summary>
/// The ID of the current message
@@ -115,35 +117,13 @@ namespace VNLib.Net.Messaging.FBM.Server
//Parse headers
ParseStatus = Helpers.ParseHeaders(vms, this, _headers, dataEncoding);
}
-
- /// <summary>
- /// Deserializes the request body into a new specified object type
- /// </summary>
- /// <typeparam name="T">The type of the object to deserialize</typeparam>
- /// <param name="jso">The <see cref="JsonSerializerOptions"/> to use while deserializing data</param>
- /// <returns>The deserialized object from the request body</returns>
- /// <exception cref="JsonException"></exception>
- public T? DeserializeBody<T>(JsonSerializerOptions? jso = default)
- {
- return BodyData.IsEmpty ? default : BodyData.AsJsonObject<T>(jso);
- }
-
- /// <summary>
- /// Gets a <see cref="JsonDocument"/> of the request body
- /// </summary>
- /// <returns>The parsed <see cref="JsonDocument"/> if parsed successfully, or null otherwise</returns>
- /// <exception cref="JsonException"></exception>
- public JsonDocument? GetBodyAsJson()
- {
- Utf8JsonReader reader = new(BodyData);
- return JsonDocument.TryParseValue(ref reader, out JsonDocument? jdoc) ? jdoc : default;
- }
+
void IReusable.Prepare()
{
ParseStatus = HeaderParseError.None;
//Alloc header buffer
- _headerBuffer = ArrayPool<char>.Shared.Rent(HeaderBufferSize);
+ _memoryManager.AllocBuffer(_memHandle, MemoryUtil.ByteCount<char>(HeaderBufferSize));
}
@@ -155,8 +135,7 @@ namespace VNLib.Net.Messaging.FBM.Server
//Clear headers before freeing buffer
_headers.Clear();
//Free header-buffer
- ArrayPool<char>.Shared.Return(_headerBuffer!);
- _headerBuffer = null;
+ _memoryManager.FreeBuffer(_memHandle);
ConnectionId = null;
MessageId = 0;
IsControlFrame = false;
@@ -165,11 +144,16 @@ namespace VNLib.Net.Messaging.FBM.Server
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- Span<char> IFBMHeaderBuffer.GetSpan(int offset, int count)
- => _headerBuffer != null ? _headerBuffer.AsSpan(offset, count) : throw new InvalidOperationException("The buffer is no longer available");
+ Span<char> IFBMHeaderBuffer.GetSpan(int offset, int count)
+ {
+ //Cast to char buffer
+ Span<char> chars = MemoryMarshal.Cast<byte, char>(_memHandle.GetSpan());
+ //Return the requested span
+ return chars.Slice(offset, count);
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- Span<char> IFBMHeaderBuffer.GetSpan() => _headerBuffer ?? throw new InvalidOperationException("The buffer is no longer available");
+ Span<char> IFBMHeaderBuffer.GetSpan() => MemoryMarshal.Cast<byte, char>(_memHandle.GetSpan());
}
}
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs b/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs
index 9ca6b4d..1e26140 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -24,14 +24,12 @@
using System;
using System.Text;
-using System.Threading;
using System.Threading.Tasks;
using VNLib.Net.Http;
using VNLib.Utils.IO;
using VNLib.Utils.Extensions;
using VNLib.Utils.Memory.Caching;
-using VNLib.Net.Messaging.FBM.Client;
namespace VNLib.Net.Messaging.FBM.Server
{
@@ -41,9 +39,9 @@ namespace VNLib.Net.Messaging.FBM.Server
/// </summary>
public sealed class FBMResponseMessage : IReusable, IFBMMessage
{
- internal FBMResponseMessage(int internalBufferSize, Encoding headerEncoding)
+ internal FBMResponseMessage(int internalBufferSize, Encoding headerEncoding, IFBMMemoryManager manager)
{
- _headerAccumulator = new HeaderDataAccumulator(internalBufferSize);
+ _headerAccumulator = new HeaderDataAccumulator(internalBufferSize, manager);
_headerEncoding = headerEncoding;
_messageEnumerator = new(this);
}
@@ -134,92 +132,76 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <summary>
/// Gets the internal message body enumerator and prepares the message for sending
/// </summary>
- /// <param name="cancellationToken">A cancellation token</param>
/// <returns>A value task that returns the message body enumerator</returns>
- internal async ValueTask<IAsyncMessageReader> GetResponseDataAsync(CancellationToken cancellationToken)
- {
- //try to buffer as much data in the header segment first
- if(MessageBody?.RemainingSize > 0 && _headerAccumulator.RemainingSize > 0)
- {
- //Read data from the message
- int read = await MessageBody.ReadAsync(_headerAccumulator.RemainingBuffer, cancellationToken);
-
- //Advance accumulator to the read bytes
- _headerAccumulator.Advance(read);
- }
- //return reusable enumerator
- return _messageEnumerator;
- }
+ internal IAsyncMessageReader GetResponseData() => _messageEnumerator;
private sealed class MessageSegmentEnumerator : IAsyncMessageReader
{
private readonly FBMResponseMessage _message;
+ private readonly ISlindingWindowBuffer<byte> _accumulator;
bool HeadersRead;
public MessageSegmentEnumerator(FBMResponseMessage message)
{
_message = message;
+ _accumulator = _message._headerAccumulator;
}
- public ReadOnlyMemory<byte> Current { get; private set; }
+ ///<inheritdoc/>
+ public ReadOnlyMemory<byte> Current => _accumulator.AccumulatedBuffer;
- public bool DataRemaining { get; private set; }
+ ///<inheritdoc/>
+ public bool DataRemaining => _message.MessageBody?.RemainingSize > 0;
+ ///<inheritdoc/>
public async ValueTask<bool> MoveNextAsync()
{
//Attempt to read header segment first
if (!HeadersRead)
{
- //Set the accumulated buffer
- Current = _message._headerAccumulator.AccumulatedBuffer;
+ /*
+ * If headers have not been read yet, we can attempt to buffer as much
+ * of the message body into the header accumulator buffer as possible. This will
+ * reduce message fragmentation.
+ */
+ if (DataRemaining && _accumulator.RemainingSize > 0)
+ {
+ int read = await _message.MessageBody.ReadAsync(_accumulator.RemainingBuffer).ConfigureAwait(false);
- //Update data remaining flag
- DataRemaining = _message.MessageBody?.RemainingSize > 0;
+ //Advance accumulator to the read bytes
+ _accumulator.Advance(read);
+ }
//Set headers read flag
HeadersRead = true;
return true;
}
- else if (_message.MessageBody?.RemainingSize > 0)
+ else if (DataRemaining)
{
- //Use the header buffer as the buffer for the message body
- Memory<byte> buffer = _message._headerAccumulator.Buffer;
+ //Reset the accumulator so we can read another segment
+ _accumulator.Reset();
//Read body segment
- int read = await _message.MessageBody.ReadAsync(buffer);
+ int read = await _message.MessageBody.ReadAsync(_accumulator.RemainingBuffer);
- //Update data remaining flag
- DataRemaining = _message.MessageBody.RemainingSize > 0;
+ //Advance accumulator to the read bytes
+ _accumulator.Advance(read);
- if (read > 0)
- {
- //Store the read segment
- Current = buffer[..read];
- return true;
- }
+ return read > 0;
}
return false;
}
+ ///<inheritdoc/>
public ValueTask DisposeAsync()
{
- //Clear current segment
- Current = default;
-
//Reset headers read flag
HeadersRead = false;
-
+
//Dispose the message body if set
- if (_message.MessageBody != null)
- {
- return _message.MessageBody.DisposeAsync();
- }
- else
- {
- return ValueTask.CompletedTask;
- }
+ return _message.MessageBody != null ? _message.MessageBody.DisposeAsync() : ValueTask.CompletedTask;
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Server/HeaderDataAccumulator.cs b/lib/Net.Messaging.FBM/src/Server/HeaderDataAccumulator.cs
index 423a26e..891c583 100644
--- a/lib/Net.Messaging.FBM/src/Server/HeaderDataAccumulator.cs
+++ b/lib/Net.Messaging.FBM/src/Server/HeaderDataAccumulator.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -23,25 +23,28 @@
*/
using System;
-using System.Buffers;
using VNLib.Utils.IO;
namespace VNLib.Net.Messaging.FBM.Server
{
+
/// <summary>
/// Reusable sliding window impl
/// </summary>
internal sealed class HeaderDataAccumulator : ISlindingWindowBuffer<byte>
{
- private readonly int BufferSize;
-
- private byte[]? _memHandle;
+ private readonly int _bufferSize;
+ private readonly IFBMMemoryManager _memManager;
+ private readonly IFBMMemoryHandle _handle;
+
- public HeaderDataAccumulator(int bufferSize)
+ public HeaderDataAccumulator(int bufferSize, IFBMMemoryManager memManager)
{
- BufferSize = bufferSize;
+ _bufferSize = bufferSize;
+ _memManager = memManager;
+ _handle = memManager.InitHandle();
}
///<inheritdoc/>
@@ -49,7 +52,7 @@ namespace VNLib.Net.Messaging.FBM.Server
///<inheritdoc/>
public int WindowEndPos { get; private set; }
///<inheritdoc/>
- public Memory<byte> Buffer => _memHandle.AsMemory();
+ public Memory<byte> Buffer => _handle.GetMemory();
///<inheritdoc/>
public void Advance(int count) => WindowEndPos += count;
@@ -67,22 +70,13 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <summary>
/// Allocates the internal message buffer
/// </summary>
- public void Prepare()
- {
- _memHandle ??= ArrayPool<byte>.Shared.Rent(BufferSize);
- }
+ public void Prepare() => _memManager.AllocBuffer(_handle, _bufferSize);
///<inheritdoc/>
public void Close()
{
Reset();
-
- if (_memHandle != null)
- {
- //Return the buffer to the pool
- ArrayPool<byte>.Shared.Return(_memHandle);
- _memHandle = null;
- }
+ _memManager.FreeBuffer(_handle);
}
}
diff --git a/lib/Net.Messaging.FBM/src/Server/IAsyncMessageReader.cs b/lib/Net.Messaging.FBM/src/Server/IAsyncMessageReader.cs
index b2abe8d..abb3600 100644
--- a/lib/Net.Messaging.FBM/src/Server/IAsyncMessageReader.cs
+++ b/lib/Net.Messaging.FBM/src/Server/IAsyncMessageReader.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Messaging.FBM
@@ -34,7 +34,7 @@ namespace VNLib.Net.Messaging.FBM.Server
internal interface IAsyncMessageReader : IAsyncEnumerator<ReadOnlyMemory<byte>>
{
/// <summary>
- /// A value that indicates if there is data remaining after a
+ /// A value that indicates if there is data remaining after a read
/// </summary>
bool DataRemaining { get; }
}
diff --git a/lib/Net.Messaging.FBM/src/Server/IFBMServerErrorHandler.cs b/lib/Net.Messaging.FBM/src/Server/IFBMServerErrorHandler.cs
new file mode 100644
index 0000000..caa4f96
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/Server/IFBMServerErrorHandler.cs
@@ -0,0 +1,49 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: IFBMServerErrorHandler.cs
+*
+* IFBMServerErrorHandler.cs is part of VNLib.Net.Messaging.FBM which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Messaging.FBM 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Net.Messaging.FBM.Server
+{
+ /// <summary>
+ /// An server side FBM protocol error handler abstraction
+ /// </summary>
+ public interface IFBMServerErrorHandler
+ {
+ /// <summary>
+ /// An exception handler for unhandled events that occur during a listening session
+ /// </summary>
+ /// <param name="ex">The exception that caused this handler to be invoked</param>
+ void OnProcessError(Exception ex);
+
+ /// <summary>
+ /// An exception handler for invalid messages that occur during a listening session.
+ /// NOTE: The context parameter is likely in an invlaid state and should be read carefully
+ /// </summary>
+ /// <param name="context">The context that the error occured while parsing on</param>
+ /// <param name="ex">The exception explaining the reason this handler was invoked</param>
+ /// <returns>A value that indicates if the server should continue processing</returns>
+ bool OnInvalidMessage(FBMContext context, Exception ex);
+ }
+}
diff --git a/lib/Net.Messaging.FBM/src/Server/IFBMServerMessageHandler.cs b/lib/Net.Messaging.FBM/src/Server/IFBMServerMessageHandler.cs
new file mode 100644
index 0000000..532db5f
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/Server/IFBMServerMessageHandler.cs
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: IFBMServerMessageHandler.cs
+*
+* IFBMServerMessageHandler.cs is part of VNLib.Net.Messaging.FBM which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Messaging.FBM is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Net.Messaging.FBM 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Messaging.FBM.Server
+{
+ /// <summary>
+ /// A server side FBM protocol handler
+ /// </summary>
+ public interface IFBMServerMessageHandler : IFBMServerErrorHandler
+ {
+ /// <summary>
+ /// Handles processing of a normal incoming message
+ /// </summary>
+ /// <param name="context">The context to process for this new message</param>
+ /// <param name="cancellationToken">A token that signals the session has been cancelled</param>
+ /// <returns>A task representing the asynchronous work</returns>
+ Task HandleMessage(FBMContext context, CancellationToken cancellationToken);
+ }
+}
diff --git a/lib/Net.Messaging.FBM/src/VNLib.Net.Messaging.FBM.csproj b/lib/Net.Messaging.FBM/src/VNLib.Net.Messaging.FBM.csproj
index 7fade2c..70a640d 100644
--- a/lib/Net.Messaging.FBM/src/VNLib.Net.Messaging.FBM.csproj
+++ b/lib/Net.Messaging.FBM/src/VNLib.Net.Messaging.FBM.csproj
@@ -35,7 +35,6 @@
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\Net.Http\src\VNLib.Net.Http.csproj" />
<ProjectReference Include="..\..\Plugins.Essentials\src\VNLib.Plugins.Essentials.csproj" />
<ProjectReference Include="..\..\Utils\src\VNLib.Utils.csproj" />
</ItemGroup>
diff --git a/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs b/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
index 7174a99..ce989b3 100644
--- a/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
+++ b/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
@@ -56,6 +56,10 @@ namespace VNLib.Net.Transport.Tcp
public override long Position { get => throw new NotSupportedException(); set => throw new NotImplementedException(); }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+ => throw new NotSupportedException("CopyToAsync is not supported");
+
+ public override void CopyTo(Stream destination, int bufferSize) => throw new NotSupportedException("CopyTo is not supported");
#endregion
//Read timeout to use when receiving data
@@ -84,70 +88,54 @@ namespace VNLib.Net.Transport.Tcp
///<inheritdoc/>
public override void Close()
- { }
+ {
+ //Call sync
+ Task closing = Transport.CloseAsync();
+ closing.GetAwaiter().GetResult();
+ }
+
///<inheritdoc/>
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
///<inheritdoc/>
public override void Flush()
{ }
///<inheritdoc/>
public override int Read(byte[] buffer, int offset, int count) => Read(buffer.AsSpan(offset, count));
+
///<inheritdoc/>
public override int Read(Span<byte> buffer) => Transport.Recv(buffer);
///<inheritdoc/>
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- //Since read returns a value, it isnt any cheaper not to alloc a task around the value-task
- return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
- }
+ //Since read returns a value, it isnt any cheaper not to alloc a task around the value-task
+ => ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+
///<inheritdoc/>
- public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
- {
- return Transport.RecvAsync(buffer, cancellationToken);
- }
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ => Transport.RecvAsync(buffer, cancellationToken);
///<inheritdoc/>
public override void Write(byte[] buffer, int offset, int count) => Write(buffer.AsSpan(offset, count));
+
///<inheritdoc/>
public override void Write(ReadOnlySpan<byte> buffer) => Transport.Send(buffer);
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- //Allow synchronous complete to avoid alloc
- return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
- }
+ ///<inheritdoc/>
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ => WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
///<inheritdoc/>
///<exception cref="IOException"></exception>
///<exception cref="ObjectDisposedException"></exception>
- public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellation = default)
- {
- return Transport.SendAsync(buffer, cancellation);
- }
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellation = default)
+ => Transport.SendAsync(buffer, cancellation);
/*
* Override dispose to intercept base cleanup until the internal release
*/
- /// <summary>
- /// Not supported
- /// </summary>
- public new void Dispose()
- {
- //Call sync
- Task closing = Transport.CloseAsync();
- closing.GetAwaiter().GetResult();
- }
- public override ValueTask DisposeAsync()
- {
- return new ValueTask(Transport.CloseAsync());
- }
-
- public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
- {
- throw new NotSupportedException("CopyToAsync is not supported");
- }
+ public override ValueTask DisposeAsync() => new (Transport.CloseAsync());
}
} \ No newline at end of file
diff --git a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs
index abb1cc1..7b391a0 100644
--- a/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs
+++ b/lib/Plugins.Essentials.ServiceStack/src/Construction/SsBuilderExtensions.cs
@@ -241,7 +241,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
//Add middleware to the chain
foreach (IHttpMiddleware mw in Config.CustomMiddleware)
{
- Instance.MiddlewareChain.AddLast(mw);
+ Instance.MiddlewareChain.Add(mw);
}
}
@@ -263,7 +263,7 @@ namespace VNLib.Plugins.Essentials.ServiceStack.Construction
plugin.OnPluginServiceEvent<IAccountSecurityProvider>(Instance.SetAccountSecProvider);
//Add all middleware to the chain
- plugin.OnPluginServiceEvent<IHttpMiddleware[]>(p => Array.ForEach(p, mw => Instance.MiddlewareChain.AddLast(mw)));
+ plugin.OnPluginServiceEvent<IHttpMiddleware[]>(p => Array.ForEach(p, mw => Instance.MiddlewareChain.Add(mw)));
}
///<inheritdoc/>
diff --git a/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs b/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs
index 3c56866..83e6a06 100644
--- a/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs
+++ b/lib/Plugins.Essentials/src/Middleware/IHttpMiddleware.cs
@@ -24,9 +24,9 @@
using System.Threading.Tasks;
-
namespace VNLib.Plugins.Essentials.Middleware
{
+
/// <summary>
/// Represents a low level intermediate request processor with high privilages, meant to add
/// functionality to entity processing.
diff --git a/lib/Plugins.Essentials/src/Middleware/IHttpMiddlewareChain.cs b/lib/Plugins.Essentials/src/Middleware/IHttpMiddlewareChain.cs
index ace0c86..0a05c70 100644
--- a/lib/Plugins.Essentials/src/Middleware/IHttpMiddlewareChain.cs
+++ b/lib/Plugins.Essentials/src/Middleware/IHttpMiddlewareChain.cs
@@ -43,13 +43,7 @@ namespace VNLib.Plugins.Essentials.Middleware
/// Adds a middleware handler to the end of the chain
/// </summary>
/// <param name="middleware">The middleware processor to add</param>
- void AddLast(IHttpMiddleware middleware);
-
- /// <summary>
- /// Adds a middleware handler to the beginning of the chain
- /// </summary>
- /// <param name="middleware">The middleware processor to add</param>
- void AddFirst(IHttpMiddleware middleware);
+ void Add(IHttpMiddleware middleware);
/// <summary>
/// Removes a middleware handler from the chain
diff --git a/lib/Plugins.Essentials/src/Middleware/MiddlewareImplAttribute.cs b/lib/Plugins.Essentials/src/Middleware/MiddlewareImplAttribute.cs
new file mode 100644
index 0000000..d5a66a4
--- /dev/null
+++ b/lib/Plugins.Essentials/src/Middleware/MiddlewareImplAttribute.cs
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials
+* File: MiddlewareImplAttribute.cs
+*
+* MiddlewareImplAttribute.cs is part of VNLib.Plugins.Essentials which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Plugins.Essentials.Middleware
+{
+ /// <summary>
+ /// Specifies optional implementation flags for a middleware instance
+ /// that loaders may use during soriting of the middleware chain
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class MiddlewareImplAttribute : Attribute
+ {
+ /// <summary>
+ /// The option flags for a middleware instance
+ /// </summary>
+ public MiddlewareImplOptions ImplOptions { get; }
+
+ /// <summary>
+ /// Creates a new <see cref="MiddlewareImplAttribute"/> instance
+ /// with the specified <see cref="MiddlewareImplOptions"/>
+ /// </summary>
+ /// <param name="implOptions">Implementation option flags</param>
+ public MiddlewareImplAttribute(MiddlewareImplOptions implOptions) => ImplOptions = implOptions;
+ }
+}
diff --git a/lib/Plugins.Essentials/src/Middleware/MiddlewareImplOptions.cs b/lib/Plugins.Essentials/src/Middleware/MiddlewareImplOptions.cs
new file mode 100644
index 0000000..1e325e3
--- /dev/null
+++ b/lib/Plugins.Essentials/src/Middleware/MiddlewareImplOptions.cs
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Essentials
+* File: MiddlewareImplOptions.cs
+*
+* MiddlewareImplOptions.cs is part of VNLib.Plugins.Essentials which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Essentials is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Essentials 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 Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+
+namespace VNLib.Plugins.Essentials.Middleware
+{
+ /// <summary>
+ /// Implementation flags for midleware implementations
+ /// </summary>
+ [Flags]
+ public enum MiddlewareImplOptions
+ {
+ /// <summary>
+ /// No flags
+ /// </summary>
+ None = 0x00,
+ /// <summary>
+ /// Prioritizes a middleware instance in the chain because
+ /// it is required for security purposes
+ /// </summary>
+ SecurityCritical = 0x01
+ }
+}
diff --git a/lib/Plugins.Essentials/src/Middleware/SemiConistentMiddlewareChain.cs b/lib/Plugins.Essentials/src/Middleware/SemiConistentMiddlewareChain.cs
index 1e1db22..5d0c472 100644
--- a/lib/Plugins.Essentials/src/Middleware/SemiConistentMiddlewareChain.cs
+++ b/lib/Plugins.Essentials/src/Middleware/SemiConistentMiddlewareChain.cs
@@ -22,6 +22,7 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+using System.Reflection;
using System.Collections.Generic;
namespace VNLib.Plugins.Essentials.Middleware
@@ -36,20 +37,23 @@ namespace VNLib.Plugins.Essentials.Middleware
private LinkedList<IHttpMiddleware> _middlewares = new();
///<inheritdoc/>
- public void AddFirst(IHttpMiddleware middleware)
+ public void Add(IHttpMiddleware middleware)
{
- lock (_middlewares)
- {
- _middlewares.AddFirst(middleware);
- }
- }
+ //Get security critical flag
+ bool isSecCritical = middleware.GetType().GetCustomAttribute<MiddlewareImplAttribute>()
+ ?.ImplOptions.HasFlag(MiddlewareImplOptions.SecurityCritical) ?? false;
- ///<inheritdoc/>
- public void AddLast(IHttpMiddleware middleware)
- {
lock (_middlewares)
{
- _middlewares.AddLast(middleware);
+ //Always add security critical middleware to the front of the chain
+ if (isSecCritical)
+ {
+ _middlewares.AddFirst(middleware);
+ }
+ else
+ {
+ _middlewares.AddLast(middleware);
+ }
}
}
diff --git a/lib/Plugins.PluginBase/src/PluginBase.cs b/lib/Plugins.PluginBase/src/PluginBase.cs
index bb42c97..839e331 100644
--- a/lib/Plugins.PluginBase/src/PluginBase.cs
+++ b/lib/Plugins.PluginBase/src/PluginBase.cs
@@ -107,6 +107,11 @@ namespace VNLib.Plugins
protected virtual string LogTemplate => $"{{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}} [{{Level:u3}}] {PluginName}: {{Message:lj}}{{NewLine}}{{Exception}}";
/// <summary>
+ /// Arguments passed to the plugin by the host application
+ /// </summary>
+ public ArgumentList HostArgs { get; private set; }
+
+ /// <summary>
/// The host application may invoke this method when the assembly is loaded and this plugin is constructed to pass
/// a configuration object to the instance. This method populates the configuration objects if applicable.
/// </summary>
@@ -133,16 +138,16 @@ namespace VNLib.Plugins
[LogInitializer]
public virtual void InitLog(string[] cmdArgs)
{
- ArgumentList args = new(cmdArgs);
+ HostArgs = new(cmdArgs);
//Open new logger config
LoggerConfiguration logConfig = new();
//Check for verbose
- if (args.HasArgument("-v"))
+ if (HostArgs.HasArgument("-v"))
{
logConfig.MinimumLevel.Verbose();
}
//Check for debug mode
- else if (args.HasArgument("-d"))
+ else if (HostArgs.HasArgument("-d"))
{
logConfig.MinimumLevel.Debug();
}
@@ -153,7 +158,7 @@ namespace VNLib.Plugins
}
//Init console log
- InitConsoleLog(args, logConfig);
+ InitConsoleLog(logConfig);
//Init file log
InitFileLog(logConfig);
@@ -162,10 +167,10 @@ namespace VNLib.Plugins
Log = new VLogProvider(logConfig);
}
- private void InitConsoleLog(ArgumentList args, LoggerConfiguration logConfig)
+ private void InitConsoleLog(LoggerConfiguration logConfig)
{
//If silent arg is not specified, open log to console
- if (!(args.HasArgument("--silent") || args.HasArgument("-s")))
+ if (!(HostArgs.HasArgument("--silent") || HostArgs.HasArgument("-s")))
{
_ = logConfig.WriteTo.Console(outputTemplate: LogTemplate, formatProvider:null);
}
diff --git a/lib/Utils.Memory/vnlib_mimalloc/Taskfile.yaml b/lib/Utils.Memory/vnlib_mimalloc/Taskfile.yaml
index ddb5d0e..e1e0da5 100644
--- a/lib/Utils.Memory/vnlib_mimalloc/Taskfile.yaml
+++ b/lib/Utils.Memory/vnlib_mimalloc/Taskfile.yaml
@@ -42,7 +42,7 @@ tasks:
- cd ../mimalloc/build && msbuild libmimalloc.sln /p:Configuration=release {{.BUILD_FLAGS}} {{.MS_ARGS}}
#init cmake build with greedy enabled
- - cmake -B./build -DENABLE_GREEDY=1
+ - cmake -B./build -DENABLE_GREEDY=1 {{.MIMALLOC_CMAKE_ARGS}}
#build solution in debug mode
- cd build && msbuild {{.PROJECT_NAME}}.sln /p:Configuration=debug {{.BUILD_FLAGS}} {{.MS_ARGS}}
diff --git a/lib/Utils/src/Async/IAsyncEventSink.cs b/lib/Utils/src/Async/IAsyncEventSink.cs
new file mode 100644
index 0000000..634b3e6
--- /dev/null
+++ b/lib/Utils/src/Async/IAsyncEventSink.cs
@@ -0,0 +1,48 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IAsyncEventSink.cs
+*
+* IAsyncEventSink.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/.
+*/
+
+namespace VNLib.Utils.Async
+{
+ /// <summary>
+ /// A type that receives events from asynchronous event sources and publishes
+ /// them to subscribers.
+ /// </summary>
+ /// <typeparam name="T">The event type</typeparam>
+ public interface IAsyncEventSink<T>
+ {
+ /// <summary>
+ /// Publishes a single event to all subscribers
+ /// </summary>
+ /// <param name="evnt">The event to publish</param>
+ /// <returns>A value that indicates if the event was successfully published to subscribers</returns>
+ bool PublishEvent(T evnt);
+
+ /// <summary>
+ /// Publishes an array of events to all subscribers
+ /// </summary>
+ /// <param name="events">The array of events to publish</param>
+ /// <returns>A value that indicates if the events were successfully published to subscribers</returns>
+ bool PublishEvents(T[] events);
+ }
+}
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs
index 6525db4..d21ceee 100644
--- a/lib/Utils/src/Extensions/MemoryExtensions.cs
+++ b/lib/Utils/src/Extensions/MemoryExtensions.cs
@@ -48,11 +48,7 @@ namespace VNLib.Utils.Extensions
/// <param name="size">The minimum size array to allocate</param>
/// <param name="zero">Should elements from 0 to size be set to default(T)</param>
/// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
- public static UnsafeMemoryHandle<T> Lease<T>(this ArrayPool<T> pool, int size, bool zero = false) where T: unmanaged
- {
- //Pool buffer handles are considered "safe" so im reusing code for now
- return new(pool, size, zero);
- }
+ public static UnsafeMemoryHandle<T> Lease<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged => new(pool, size, zero);
/// <summary>
/// Retreives a buffer that is at least the reqested length, and clears the array from 0-size.
@@ -81,32 +77,10 @@ namespace VNLib.Utils.Extensions
/// </summary>
/// <returns>The string representation of the buffer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string ToString<T>(this T charBuffer) where T: IMemoryHandle<char>
- {
- return charBuffer.Span.ToString();
- }
-
- /// <summary>
- /// Wraps the <see cref="MemoryHandle{T}"/> instance in System.Buffers.MemoryManager
- /// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles.
- /// </summary>
- /// <typeparam name="T">The unmanaged data type</typeparam>
- /// <param name="handle"></param>
- /// <param name="ownsHandle">
- /// A value that indicates if the new <see cref="MemoryManager{T}"/> owns the handle.
- /// When <c>true</c>, the new <see cref="MemoryManager{T}"/> maintains the lifetime of the handle.
- /// </param>
- /// <returns>The <see cref="MemoryManager{T}"/> wrapper</returns>
- /// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryManager<T> ToMemoryManager<T>(this MemoryHandle<T> handle, bool ownsHandle = true) where T : unmanaged
- {
- _ = handle ?? throw new ArgumentNullException(nameof(handle));
- return new SysBufferMemoryManager<T>(handle, ownsHandle);
- }
+ public static string ToString<T>(this T charBuffer) where T : IMemoryHandle<char> => charBuffer.Span.ToString();
/// <summary>
- /// Wraps the <see cref="VnTempBuffer{T}"/> instance in System.Buffers.MemoryManager
+ /// Wraps the <see cref="IMemoryHandle{T}"/> instance in System.Buffers.MemoryManager
/// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles.
/// </summary>
/// <typeparam name="T">The unmanaged data type</typeparam>
@@ -117,15 +91,12 @@ namespace VNLib.Utils.Extensions
/// </param>
/// <returns>The <see cref="MemoryManager{T}"/> wrapper</returns>
/// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks>
+ /// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryManager<T> ToMemoryManager<T>(this VnTempBuffer<T> handle, bool ownsHandle = true) where T : unmanaged
- {
- _ = handle ?? throw new ArgumentNullException(nameof(handle));
- return new SysBufferMemoryManager<T>(handle, ownsHandle);
- }
+ public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle) => new SysBufferMemoryManager<T>(handle, ownsHandle);
/// <summary>
- /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="Win32PrivateHeap"/> instance
+ /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance
/// of the specified number of elements
/// </summary>
/// <typeparam name="T">The unmanaged data type</typeparam>
@@ -133,10 +104,14 @@ namespace VNLib.Utils.Extensions
/// <param name="size">The number of elements to allocate on the heap</param>
/// <param name="zero">Optionally zeros conents of the block when allocated</param>
/// <returns>The <see cref="MemoryManager{T}"/> wrapper around the block of memory</returns>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, nuint size, bool zero = false) where T : unmanaged
{
- return new SysBufferMemoryManager<T>(heap, size, zero);
+ MemoryHandle<T> handle = heap.Alloc<T>(size, zero);
+ return new SysBufferMemoryManager<T>(handle, true);
}
/// <summary>
@@ -163,10 +138,10 @@ namespace VNLib.Utils.Extensions
/// </returns>
//Method only exists for consistancy since unsafe handles are always 32bit
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T: unmanaged => handle.IntLength;
+ public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged => handle.IntLength;
/// <summary>
- /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="Win32PrivateHeap"/> instance
+ /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance
/// of the specified number of elements
/// </summary>
/// <typeparam name="T">The unmanaged data type</typeparam>
@@ -181,6 +156,7 @@ namespace VNLib.Utils.Extensions
{
return size >= 0 ? DirectAlloc<T>(heap, (nuint)size, zero) : throw new ArgumentOutOfRangeException(nameof(size), "The size paramter must be a positive integer");
}
+
/// <summary>
/// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks
/// </summary>
@@ -194,6 +170,7 @@ namespace VNLib.Utils.Extensions
{
return elements >= 0 ? memory.GetOffset((nuint)elements) : throw new ArgumentOutOfRangeException(nameof(elements), "The elements paramter must be a positive integer");
}
+
/// <summary>
/// Resizes the current handle on the heap
/// </summary>
@@ -204,7 +181,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Resize<T>(this MemoryHandle<T> memory, nint elements) where T : unmanaged
+ public static void Resize<T>(this IResizeableMemoryHandle<T> memory, nint elements)
{
if (elements < 0)
{
@@ -224,7 +201,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, nint count) where T : unmanaged
+ public static void ResizeIfSmaller<T>(this IResizeableMemoryHandle<T> handle, nint count)
{
if(count < 0)
{
@@ -244,7 +221,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, nuint count) where T : unmanaged
+ public static void ResizeIfSmaller<T>(this IResizeableMemoryHandle<T> handle, nuint count)
{
//Check handle size
if(handle.Length < count)
@@ -254,6 +231,52 @@ namespace VNLib.Utils.Extensions
}
}
+ /// <summary>
+ /// Gets a reference to the element at the specified offset from the base
+ /// address of the <see cref="MemoryHandle{T}"/>
+ /// </summary>
+ /// <param name="block"></param>
+ /// <param name="offset">The element offset from the base address to add to the returned reference</param>
+ /// <returns>The reference to the item at the desired offset</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetOffsetRef<T>(this IMemoryHandle<T> block, nuint offset)
+ {
+ _ = block ?? throw new ArgumentNullException(nameof(block));
+
+ if (offset >= block.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ return ref Unsafe.Add(ref block.GetReference(), offset);
+ }
+
+ /// <summary>
+ /// Gets a reference to the element at the specified offset from the base
+ /// address of the <see cref="MemoryHandle{T}"/> and casts it to a byte reference
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="block"></param>
+ /// <param name="offset">The number of elements to offset the base reference by</param>
+ /// <returns>The reinterpreted byte reference at the first byte of the element offset</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref byte GetByteOffsetRef<T>(this IMemoryHandle<T> block, nuint offset)
+ {
+ _ = block ?? throw new ArgumentNullException(nameof(block));
+
+ if (offset >= block.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ //Get the base reference, then offset by the desired number of elements and cast to a byte reference
+ ref T baseRef = ref block.GetReference();
+ ref T offsetRef = ref Unsafe.Add(ref baseRef, offset);
+ return ref Unsafe.As<T, byte>(ref offsetRef);
+ }
/// <summary>
/// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/>
@@ -265,7 +288,7 @@ namespace VNLib.Utils.Extensions
/// <returns>The offset span</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, nuint offset, int size) where T: unmanaged
+ public static Span<T> GetOffsetSpan<T>(this IMemoryHandle<T> block, nuint offset, int size)
{
_ = block ?? throw new ArgumentNullException(nameof(block));
@@ -282,9 +305,10 @@ namespace VNLib.Utils.Extensions
MemoryUtil.CheckBounds(block, offset, (nuint)size);
//Get long offset from the destination handle
- void* ofPtr = block.GetOffset(offset);
- return new Span<T>(ofPtr, size);
+ ref T ofPtr = ref GetOffsetRef(block, offset);
+ return MemoryMarshal.CreateSpan(ref ofPtr, size);
}
+
/// <summary>
/// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/>
/// </summary>
@@ -295,7 +319,7 @@ namespace VNLib.Utils.Extensions
/// <returns>The offset span</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, nint offset, int size) where T : unmanaged
+ public static unsafe Span<T> GetOffsetSpan<T>(this IMemoryHandle<T> block, nint offset, int size)
{
return offset >= 0 ? block.GetOffsetSpan((nuint)offset, size) : throw new ArgumentOutOfRangeException(nameof(offset));
}
@@ -310,7 +334,7 @@ namespace VNLib.Utils.Extensions
/// <returns>The new <see cref="SubSequence{T}"/> within the block</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, nuint offset, int size) where T : unmanaged => new (block, offset, size);
+ public static SubSequence<T> GetSubSequence<T>(this IMemoryHandle<T> block, nuint offset, int size) => new (block, offset, size);
/// <summary>
/// Gets a <see cref="SubSequence{T}"/> window within the current block
@@ -322,7 +346,7 @@ namespace VNLib.Utils.Extensions
/// <returns>The new <see cref="SubSequence{T}"/> within the block</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, nint offset, int size) where T : unmanaged
+ public static SubSequence<T> GetSubSequence<T>(this IMemoryHandle<T> block, nint offset, int size)
{
return offset >= 0 ? new (block, (nuint)offset, size) : throw new ArgumentOutOfRangeException(nameof(offset));
}
@@ -334,10 +358,7 @@ namespace VNLib.Utils.Extensions
/// <typeparam name="T">The unmanged data type to provide allocations from</typeparam>
/// <returns>The new <see cref="MemoryPool{T}"/> heap wrapper.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged
- {
- return new PrivateBuffersMemoryPool<T>(heap, maxBufferSize);
- }
+ public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged => new PrivateBuffersMemoryPool<T>(heap, maxBufferSize);
/// <summary>
/// Allocates a structure of the specified type on the current unmanged heap and zero's its memory
@@ -349,14 +370,8 @@ namespace VNLib.Utils.Extensions
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged
- {
- //Allocate the struct on the heap and zero memory it points to
- IntPtr handle = heap.Alloc(1, (nuint)sizeof(T), true);
- //returns the handle
- return (T*)handle;
- }
-
+ public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged => (T*)heap.Alloc(1, (nuint)sizeof(T), true);
+
/// <summary>
/// Frees a structure at the specified address from the this heap.
/// This must be the same heap the structure was allocated from
@@ -468,7 +483,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void WriteAndResize<T>(this MemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged
+ public static void WriteAndResize<T>(this IResizeableMemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged
{
handle.Resize(input.Length);
MemoryUtil.Copy(input, handle, 0);
@@ -618,6 +633,7 @@ namespace VNLib.Utils.Extensions
{
return GetBytes(enc, chars.AsSpan(offset, charCount), ref writer, flush);
}
+
/// <summary>
/// Encodes a set of characters in the input characters span and any characters
/// in the internal buffer into a sequence of bytes that are stored in the input
@@ -639,6 +655,7 @@ namespace VNLib.Utils.Extensions
writer.Advance(written);
return written;
}
+
/// <summary>
/// Encodes a set of characters in the input characters span and any characters
/// in the internal buffer into a sequence of bytes that are stored in the input
@@ -657,6 +674,7 @@ namespace VNLib.Utils.Extensions
writer.Advance(written);
return written;
}
+
/// <summary>
/// Decodes a character buffer in the input characters span and any characters
/// in the internal buffer into a sequence of bytes that are stored in the input
@@ -683,6 +701,7 @@ namespace VNLib.Utils.Extensions
/// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true);
+
/// <summary>
/// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer
/// </summary>
@@ -711,6 +730,7 @@ namespace VNLib.Utils.Extensions
Range sliceRange = new(start, arr.Length - start);
return RuntimeHelpers.GetSubArray(arr, sliceRange);
}
+
/// <summary>
/// Slices the current array by the specified starting offset to including the
/// speciifed number of items
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
index 97cef03..7ac56a6 100644
--- a/lib/Utils/src/IO/VnMemoryStream.cs
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -24,7 +24,9 @@
using System;
using System.IO;
+using System.Buffers;
using System.Threading;
+using System.Diagnostics;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
@@ -33,6 +35,7 @@ using VNLib.Utils.Extensions;
namespace VNLib.Utils.IO
{
+
/// <summary>
/// Provides an unmanaged memory stream. Desigend to help reduce garbage collector load for
/// high frequency memory operations. Similar to <see cref="UnmanagedMemoryStream"/>
@@ -44,35 +47,39 @@ namespace VNLib.Utils.IO
private bool _isReadonly;
//Memory
- private readonly MemoryHandle<byte> _buffer;
+ private readonly IResizeableMemoryHandle<byte> _buffer;
//Default owns handle
private readonly bool OwnsHandle = true;
/// <summary>
/// Creates a new <see cref="VnMemoryStream"/> pointing to the begining of memory, and consumes the handle.
/// </summary>
- /// <param name="handle"><see cref="MemoryHandle{T}"/> to consume</param>
+ /// <param name="handle"><see cref="IResizeableMemoryHandle{T}"/> to consume</param>
/// <param name="length">Length of the stream</param>
/// <param name="readOnly">Should the stream be readonly?</param>
/// <exception cref="ArgumentException"></exception>
/// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns>
- public static VnMemoryStream ConsumeHandle(MemoryHandle<byte> handle, nint length, bool readOnly) => FromHandle(handle, true, length, readOnly);
+ public static VnMemoryStream ConsumeHandle(IResizeableMemoryHandle<byte> handle, nint length, bool readOnly) => FromHandle(handle, true, length, readOnly);
/// <summary>
/// Creates a new <see cref="VnMemoryStream"/> from the supplied memory handle
/// of the initial length. This function also accepts a value that indicates if this stream
/// owns the memory handle, which will cause it to be disposed when the stream is disposed.
/// </summary>
- /// <param name="handle"><see cref="MemoryHandle{T}"/> to consume</param>
+ /// <param name="handle"><see cref="IResizeableMemoryHandle{T}"/> to consume</param>
/// <param name="length">The initial length of the stream</param>
/// <param name="readOnly">Should the stream be readonly?</param>
/// <param name="ownsHandle">A value that indicates if the current stream owns the memory handle</param>
/// <exception cref="ArgumentException"></exception>
/// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns>
- public static VnMemoryStream FromHandle(MemoryHandle<byte> handle, bool ownsHandle, nint length, bool readOnly)
+ public static VnMemoryStream FromHandle(IResizeableMemoryHandle<byte> handle, bool ownsHandle, nint length, bool readOnly)
{
- handle.ThrowIfClosed();
- return new VnMemoryStream(handle, length, readOnly, ownsHandle);
+ //Check the handle
+ _ = handle ?? throw new ArgumentNullException(nameof(handle));
+
+ return handle.CanRealloc || readOnly
+ ? new VnMemoryStream(handle, length, readOnly, ownsHandle)
+ : throw new ArgumentException("The supplied memory handle must be resizable on a writable stream", nameof(handle));
}
/// <summary>
@@ -155,8 +162,11 @@ namespace VNLib.Utils.IO
/// <param name="length">The length property of the stream</param>
/// <param name="readOnly">Is the stream readonly (should mostly be true!)</param>
/// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param>
- private VnMemoryStream(MemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle)
+ private VnMemoryStream(IResizeableMemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle)
{
+ Debug.Assert(length >= 0, "Length must be positive");
+ Debug.Assert(buffer.CanRealloc || readOnly, "The supplied buffer is not resizable on a writable stream");
+
OwnsHandle = ownsHandle;
_buffer = buffer; //Consume the handle
_length = length; //Store length of the buffer
@@ -200,8 +210,8 @@ namespace VNLib.Utils.IO
{
throw new IOException("The destinaion stream is not writeable");
}
-
- do
+
+ while (LenToPosDiff > 0)
{
//Calc the remaining bytes to read no larger than the buffer size
int bytesToRead = (int)Math.Min(LenToPosDiff, bufferSize);
@@ -213,8 +223,7 @@ namespace VNLib.Utils.IO
//Update position
_position += bytesToRead;
-
- } while (LenToPosDiff > 0);
+ }
}
/// <summary>
@@ -231,37 +240,47 @@ namespace VNLib.Utils.IO
public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
_ = destination ?? throw new ArgumentNullException(nameof(destination));
-
+
+ if (bufferSize < 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than 0");
+ }
+
if (!destination.CanWrite)
{
throw new IOException("The destinaion stream is not writeable");
}
- cancellationToken.ThrowIfCancellationRequested();
-
- /*
- * Alloc temp copy buffer. This is a requirement because
- * the stream may be larger than an int32 so it must be
- * copied by segment
- */
+ cancellationToken.ThrowIfCancellationRequested();
- using VnTempBuffer<byte> copyBuffer = new(bufferSize);
-
- do
+ if(_length < Int32.MaxValue)
{
- //read from internal stream
- int read = Read(copyBuffer);
+ //Safe to alloc a memory manager to do copy
+ using MemoryManager<byte> asMemManager = _buffer.ToMemoryManager(false);
+
+ /*
+ * CopyTo starts at the current position, as if calling Read()
+ * so the reader must be offset to match and the _length gives us the
+ * actual length of the stream and therefor the segment size
+ */
- if(read <= 0)
+ while(LenToPosDiff > 0)
{
- break;
- }
+ int blockSize = Math.Min((int)LenToPosDiff, bufferSize);
+ Memory<byte> window = asMemManager.Memory.Slice((int)_position, blockSize);
- //write async
- await destination.WriteAsync(copyBuffer.AsMemory(0, read), cancellationToken);
+ //write async
+ await destination.WriteAsync(window, cancellationToken);
- } while (true);
-
+ //Update position
+ _position+= bufferSize;
+ }
+ }
+ else
+ {
+ //TODO support 64bit memory stream copy
+ throw new NotSupportedException("64bit async copies are currently not supported");
+ }
}
/// <summary>
@@ -349,13 +368,13 @@ namespace VNLib.Utils.IO
}
//get the value at the current position
- byte* ptr = _buffer.GetOffset(_position);
+ ref byte ptr = ref _buffer.GetByteOffsetRef((nuint)_position);
//Increment position
_position++;
//Return value
- return *ptr;
+ return ptr;
}
/*
diff --git a/lib/Utils/src/Memory/VnTempBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs
index 5f5f831..92a2022 100644
--- a/lib/Utils/src/Memory/VnTempBuffer.cs
+++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs
@@ -24,6 +24,8 @@
using System;
using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using VNLib.Utils.Extensions;
@@ -33,7 +35,7 @@ namespace VNLib.Utils.Memory
/// A disposable temporary buffer from shared ArrayPool
/// </summary>
/// <typeparam name="T">Type of buffer to create</typeparam>
- public sealed class VnTempBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>, IMemoryOwner<T>
+ public sealed class ArrayPoolBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>, IMemoryOwner<T>
{
private readonly ArrayPool<T> Pool;
@@ -64,25 +66,29 @@ namespace VNLib.Utils.Memory
}
///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref T GetReference() => ref MemoryMarshal.GetArrayDataReference(Buffer);
+
+ ///<inheritdoc/>
Memory<T> IMemoryOwner<T>.Memory => AsMemory();
/// <summary>
- /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from shared array-pool
+ /// Allocates a new <see cref="ArrayPoolBuffer{BufType}"/> with a new buffer from shared array-pool
/// </summary>
/// <param name="minSize">Minimum size of the buffer</param>
/// <param name="zero">Set the zero memory flag on close</param>
- public VnTempBuffer(int minSize, bool zero = false) :this(ArrayPool<T>.Shared, minSize, zero)
- {}
+ public ArrayPoolBuffer(int minSize, bool zero = false) :this(ArrayPool<T>.Shared, minSize, zero)
+ { }
/// <summary>
- /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from specified array-pool
+ /// Allocates a new <see cref="ArrayPoolBuffer{BufType}"/> with a new buffer from specified array-pool
/// </summary>
/// <param name="pool">The <see cref="ArrayPool{T}"/> to allocate from and return to</param>
/// <param name="minSize">Minimum size of the buffer</param>
/// <param name="zero">Set the zero memory flag on close</param>
- public VnTempBuffer(ArrayPool<T> pool, int minSize, bool zero = false)
+ public ArrayPoolBuffer(ArrayPool<T> pool, int minSize, bool zero = false)
{
- Pool = pool;
+ Pool = pool ?? throw new ArgumentNullException(nameof(pool));
Buffer = pool.Rent(minSize, zero);
InitSize = minSize;
}
@@ -126,44 +132,64 @@ namespace VNLib.Utils.Memory
Check();
return new Memory<T>(Buffer, 0, InitSize);
}
-
+
/// <summary>
/// Gets a memory structure around the internal buffer
/// </summary>
/// <param name="count">The number of elements included in the result</param>
- /// <param name="start">A value specifying the begining index of the buffer to include</param>
/// <returns>A memory structure over the buffer</returns>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public Memory<T> AsMemory(int start, int count)
- {
- Check();
- return new Memory<T>(Buffer, start, count);
- }
+ public Memory<T> AsMemory(int count) => AsMemory()[..count];
/// <summary>
/// Gets a memory structure around the internal buffer
/// </summary>
/// <param name="count">The number of elements included in the result</param>
+ /// <param name="start">A value specifying the begining index of the buffer to include</param>
/// <returns>A memory structure over the buffer</returns>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public Memory<T> AsMemory(int count)
+ public Memory<T> AsMemory(int start, int count) => AsMemory().Slice(start, count);
+
+ /// <summary>
+ /// Gets an array segment around the internal buffer
+ /// </summary>
+ /// <returns>The internal array segment</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public ArraySegment<T> AsArraySegment()
{
Check();
- return new Memory<T>(Buffer, 0, count);
+ return new ArraySegment<T>(Buffer, 0, InitSize);
}
-
- /*
- * Allow implict casts to span/arrayseg/memory
- */
- public static implicit operator Memory<T>(VnTempBuffer<T> buf) => buf == null ? Memory<T>.Empty : buf.ToMemory();
- public static implicit operator Span<T>(VnTempBuffer<T> buf) => buf == null ? Span<T>.Empty : buf.ToSpan();
- public static implicit operator ArraySegment<T>(VnTempBuffer<T> buf) => buf == null ? ArraySegment<T>.Empty : buf.ToArraySegment();
- public Memory<T> ToMemory() => Disposed ? Memory<T>.Empty : Buffer.AsMemory(0, InitSize);
- public Span<T> ToSpan() => Disposed ? Span<T>.Empty : Buffer.AsSpan(0, InitSize);
- public ArraySegment<T> ToArraySegment() => Disposed ? ArraySegment<T>.Empty : new(Buffer, 0, InitSize);
+ /// <summary>
+ /// Gets an array segment around the internal buffer
+ /// </summary>
+ /// <returns>The internal array segment</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public ArraySegment<T> AsArraySegment(int start, int count)
+ {
+ if(start< 0 || count < 0)
+ {
+ throw new ArgumentOutOfRangeException(start < 0 ? nameof(start) : nameof(count), "Cannot be less than zero");
+ }
+
+ MemoryUtil.CheckBounds(Buffer, (uint)start, (uint)count);
+
+ Check();
+ return new ArraySegment<T>(Buffer, start, count);
+ }
+
+ //Pin, will also check bounds
+ ///<inheritdoc/>
+ public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex);
+
+ void IPinnable.Unpin()
+ {
+ //Gchandle will manage the unpin
+ }
/// <summary>
/// Returns buffer to shared array-pool
@@ -179,16 +205,7 @@ namespace VNLib.Utils.Memory
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}
- //Pin, will also check bounds
- ///<inheritdoc/>
- public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex);
-
- void IPinnable.Unpin()
- {
- //Gchandle will manage the unpin
- }
-
///<inheritdoc/>
- ~VnTempBuffer() => Free();
+ ~ArrayPoolBuffer() => Free();
}
} \ No newline at end of file
diff --git a/lib/Utils/src/Memory/Caching/IReusable.cs b/lib/Utils/src/Memory/Caching/IReusable.cs
index 618878f..4472ad3 100644
--- a/lib/Utils/src/Memory/Caching/IReusable.cs
+++ b/lib/Utils/src/Memory/Caching/IReusable.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -25,14 +25,20 @@
namespace VNLib.Utils.Memory.Caching
{
/// <summary>
- /// Allows for use within a <see cref="ReusableStore{T}"/>, this object is intended to be reused heavily
+ /// Allows for use within a <see cref="ObjectRental{T}"/>, this object is intended to be reused heavily
/// </summary>
public interface IReusable
{
/// <summary>
/// The instance should prepare itself for use (or re-use)
+ /// <para>
+ /// This method is guarunteed to be called directly after a constructor
+ /// when a new instance is allocated and before it is ever returned to a
+ /// caller.
+ /// </para>
/// </summary>
void Prepare();
+
/// <summary>
/// The intance is being returned and should determine if it's state is reusabled
/// </summary>
diff --git a/lib/Utils/src/Memory/HeapCreation.cs b/lib/Utils/src/Memory/HeapCreation.cs
index 9ef9fdb..835226c 100644
--- a/lib/Utils/src/Memory/HeapCreation.cs
+++ b/lib/Utils/src/Memory/HeapCreation.cs
@@ -49,6 +49,10 @@ namespace VNLib.Utils.Memory
/// <summary>
/// Specifies that the requested heap will be a shared heap for the process/library
/// </summary>
- Shared = 0x04
+ Shared = 0x04,
+ /// <summary>
+ /// Specifies that the heap will support block reallocation
+ /// </summary>
+ SupportsRealloc = 0x08,
}
} \ No newline at end of file
diff --git a/lib/Utils/src/Memory/IMemoryHandle.cs b/lib/Utils/src/Memory/IMemoryHandle.cs
index cf19ce9..f4e1a36 100644
--- a/lib/Utils/src/Memory/IMemoryHandle.cs
+++ b/lib/Utils/src/Memory/IMemoryHandle.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -42,6 +42,12 @@ namespace VNLib.Utils.Memory
/// Gets the internal block as a span
/// </summary>
Span<T> Span { get; }
+
+ /// <summary>
+ /// Gets a reference to the first element in the block
+ /// </summary>
+ /// <returns>The reference</returns>
+ ref T GetReference();
}
}
diff --git a/lib/Utils/src/Memory/IResizeableMemoryHandle.cs b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs
new file mode 100644
index 0000000..f788b48
--- /dev/null
+++ b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs
@@ -0,0 +1,52 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: IResizeableMemoryHandle.cs
+*
+* IResizeableMemoryHandle.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+
+namespace VNLib.Utils.Memory
+{
+
+ /// <summary>
+ /// Represents a memory handle that can be resized in place.
+ /// </summary>
+ /// <typeparam name="T">The data type</typeparam>
+ public interface IResizeableMemoryHandle<T> : IMemoryHandle<T>
+ {
+ /// <summary>
+ /// Gets a value indicating whether the handle supports resizing in place
+ /// </summary>
+ bool CanRealloc { get; }
+
+ /// <summary>
+ /// Resizes a memory handle to a new number of elements.
+ /// </summary>
+ /// <remarks>
+ /// Even if a handle is resizable resizing may not be supported for all types of handles.
+ /// </remarks>
+ /// <param name="elements">The new number of elements to resize the handle to</param>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="NotSupportedException"></exception>
+ void Resize(nuint elements);
+ }
+} \ No newline at end of file
diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs
index 30d2b99..4d2ff0c 100644
--- a/lib/Utils/src/Memory/MemoryHandle.cs
+++ b/lib/Utils/src/Memory/MemoryHandle.cs
@@ -40,8 +40,16 @@ namespace VNLib.Utils.Memory
/// Handles are configured to address blocks larger than 2GB,
/// so some properties may raise exceptions if large blocks are used.
/// </remarks>
- public sealed class MemoryHandle<T> : SafeHandleZeroOrMinusOneIsInvalid, IMemoryHandle<T>, IEquatable<MemoryHandle<T>> where T : unmanaged
+ public sealed class MemoryHandle<T> :
+ SafeHandleZeroOrMinusOneIsInvalid,
+ IResizeableMemoryHandle<T>,
+ IMemoryHandle<T>,
+ IEquatable<MemoryHandle<T>>
+ where T : unmanaged
{
+ private readonly bool ZeroMemory;
+ private readonly IUnmangedHeap Heap;
+ private nuint _length;
/// <summary>
/// New <typeparamref name="T"/>* pointing to the base of the allocated block
@@ -79,15 +87,11 @@ namespace VNLib.Utils.Memory
}
}
- private readonly bool ZeroMemory;
- private readonly IUnmangedHeap Heap;
- private nuint _length;
-
///<inheritdoc/>
- public nuint Length
+ public nuint Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _length;
+ get => _length;
}
/// <summary>
@@ -101,6 +105,13 @@ namespace VNLib.Utils.Memory
get => MemoryUtil.ByteCount<T>(_length);
}
+ ///<inheritdoc/>
+ public bool CanRealloc
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Heap != null && Heap.CreationFlags.HasFlag(HeapCreation.SupportsRealloc);
+ }
+
/// <summary>
/// Creates a new memory handle, for which is holds ownership, and allocates the number of elements specified on the heap.
/// </summary>
@@ -128,7 +139,7 @@ namespace VNLib.Utils.Memory
/// when accessed, however <see cref="IMemoryHandle{T}"/> operations are
/// considered "safe" meaning they should never raise excpetions
/// </summary>
- public MemoryHandle():base(false)
+ public MemoryHandle() : base(false)
{
_length = 0;
Heap = null!;
@@ -151,7 +162,7 @@ namespace VNLib.Utils.Memory
* If resize raises an exception the current block pointer
* should still be valid, if its not, the pointer should
* be set to 0/-1, which will be considered invalid anyway
- */
+ */
Heap.Resize(ref handle, elements, (nuint)sizeof(T), ZeroMemory);
@@ -161,14 +172,14 @@ namespace VNLib.Utils.Memory
//Catch the disposed exception so we can invalidate the current ptr
catch (ObjectDisposedException)
{
- base.handle = IntPtr.Zero;
+ SetHandle(IntPtr.Zero);
//Set as invalid so release does not get called
- base.SetHandleAsInvalid();
+ SetHandleAsInvalid();
//Propagate the exception
throw;
}
}
-
+
/// <summary>
/// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks
/// </summary>
@@ -190,7 +201,14 @@ namespace VNLib.Utils.Memory
T* bs = ((T*)handle) + elements;
return bs;
}
-
+
+ ///<inheritdoc/>
+ public ref T GetReference()
+ {
+ this.ThrowIfClosed();
+ return ref MemoryUtil.GetRef<T>(handle);
+ }
+
///<inheritdoc/>
///<exception cref="ObjectDisposedException"></exception>
///<exception cref="ArgumentOutOfRangeException"></exception>
@@ -200,14 +218,14 @@ namespace VNLib.Utils.Memory
///</remarks>
public unsafe MemoryHandle Pin(int elementIndex)
{
- if(elementIndex < 0)
+ if (elementIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(elementIndex));
}
//Get ptr and guard checks before adding the referrence
T* ptr = GetOffset((nuint)elementIndex);
-
+
bool addRef = false;
//use the pinned field as success val
DangerousAddRef(ref addRef);
@@ -216,7 +234,7 @@ namespace VNLib.Utils.Memory
? throw new ObjectDisposedException("Failed to increase referrence count on the memory handle because it was released")
: new MemoryHandle(ptr, pinnable: this);
}
-
+
///<inheritdoc/>
///<exception cref="ObjectDisposedException"></exception>
public void Unpin()
@@ -226,11 +244,7 @@ namespace VNLib.Utils.Memory
}
///<inheritdoc/>
- protected override bool ReleaseHandle()
- {
- //Return result of free, only if the handle is valid
- return Heap.Free(ref handle);
- }
+ protected override bool ReleaseHandle() => Heap.Free(ref handle);
/// <summary>
/// Determines if the memory blocks are equal by comparing their base addresses.
@@ -243,14 +257,13 @@ namespace VNLib.Utils.Memory
{
return other != null && (IsClosed | other.IsClosed) == false && _length == other._length && handle == other.handle;
}
-
+
///<inheritdoc/>
public override bool Equals(object? obj) => obj is MemoryHandle<T> oHandle && Equals(oHandle);
-
+
///<inheritdoc/>
public override int GetHashCode() => base.GetHashCode();
-
///<inheritdoc/>
public static implicit operator Span<T>(MemoryHandle<T> handle)
{
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index 8cc9736..0261bdf 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -31,7 +31,7 @@ using System.Globalization;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
-using VNLib.Utils.Extensions;
+using VNLib.Utils.Resources;
using VNLib.Utils.Memory.Diagnostics;
namespace VNLib.Utils.Memory
@@ -113,7 +113,7 @@ namespace VNLib.Utils.Memory
Trace.WriteLineIf(globalZero, "Shared heap global zero enabled");
Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable, globalZero), LazyThreadSafetyMode.PublicationOnly);
-
+
//Cleanup the heap on process exit
AppDomain.CurrentDomain.DomainUnload += DomainUnloaded;
@@ -165,7 +165,7 @@ namespace VNLib.Utils.Memory
string? rawFlagsEnv = Environment.GetEnvironmentVariable(SHARED_HEAP_RAW_FLAGS);
//Default flags
- HeapCreation cFlags = HeapCreation.UseSynchronization;
+ HeapCreation cFlags = HeapCreation.UseSynchronization | HeapCreation.SupportsRealloc;
/*
* We need to set the shared flag and the synchronziation flag.
@@ -235,7 +235,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">Unmanged datatype</typeparam>
/// <param name="block">Block of memory to be cleared</param>
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
- public static void UnsafeZeroMemory<T>(ReadOnlySpan<T> block) where T : unmanaged
+ public static void UnsafeZeroMemory<T>(ReadOnlySpan<T> block) where T : struct
{
if (block.IsEmpty)
{
@@ -244,11 +244,11 @@ namespace VNLib.Utils.Memory
uint byteSize = ByteCount<T>((uint)block.Length);
- fixed (void* ptr = &MemoryMarshal.GetReference(block))
- {
- //Calls memset
- Unsafe.InitBlock(ptr, 0, byteSize);
- }
+ ref T r0 = ref MemoryMarshal.GetReference(block);
+ ref byte byteRef = ref Unsafe.As<T, byte>(ref r0);
+
+ //Calls memset
+ Unsafe.InitBlock(ref byteRef, 0, byteSize);
}
/// <summary>
@@ -257,7 +257,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">Unmanged datatype</typeparam>
/// <param name="block">Block of memory to be cleared</param>
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
- public static void UnsafeZeroMemory<T>(ReadOnlyMemory<T> block) where T : unmanaged
+ public static void UnsafeZeroMemory<T>(ReadOnlyMemory<T> block) where T : struct
{
if (block.IsEmpty)
{
@@ -284,7 +284,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">The unmanaged</typeparam>
/// <param name="block">The block of memory to initialize</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InitializeBlock<T>(Span<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block);
+ public static void InitializeBlock<T>(Span<T> block) where T : struct => UnsafeZeroMemory<T>(block);
/// <summary>
/// Initializes a block of memory with zeros
@@ -292,7 +292,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">The unmanaged</typeparam>
/// <param name="block">The block of memory to initialize</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void InitializeBlock<T>(Memory<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block);
+ public static void InitializeBlock<T>(Memory<T> block) where T : struct => UnsafeZeroMemory<T>(block);
/// <summary>
/// Zeroes a block of memory of the given unmanaged type
@@ -303,16 +303,13 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void InitializeBlock<T>(T* block, int itemCount) where T : unmanaged
{
- if (itemCount == 0)
+ if (itemCount <= 0 || block == null)
{
return;
}
- //Get the size of the structure
- int size = sizeof(T);
-
//Zero block
- Unsafe.InitBlock(block, 0, (uint)(size * itemCount));
+ Unsafe.InitBlock(block, 0, ByteCount<T>((uint)itemCount));
}
/// <summary>
@@ -321,33 +318,24 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">The unmanaged type to zero</typeparam>
/// <param name="block">A pointer to the block of memory to zero</param>
/// <param name="itemCount">The number of elements in the block to zero</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitializeBlock<T>(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount);
/// <summary>
/// Zeroes a block of memory pointing to the structure
/// </summary>
/// <typeparam name="T">The structure type</typeparam>
- /// <param name="block">The pointer to the allocated structure</param>
- public static void ZeroStruct<T>(IntPtr block)
- {
- //get thes size of the structure does not have to be primitive type
- int size = Unsafe.SizeOf<T>();
- //Zero block
- Unsafe.InitBlock(block.ToPointer(), 0, (uint)size);
- }
+ /// <param name="structPtr">The pointer to the allocated structure</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ZeroStruct<T>(void* structPtr) => Unsafe.InitBlock(structPtr, 0, (uint)Unsafe.SizeOf<T>());
/// <summary>
/// Zeroes a block of memory pointing to the structure
/// </summary>
/// <typeparam name="T">The structure type</typeparam>
- /// <param name="structPtr">The pointer to the allocated structure</param>
- public static void ZeroStruct<T>(void* structPtr)
- {
- //get thes size of the structure
- int size = Unsafe.SizeOf<T>();
- //Zero block
- Unsafe.InitBlock(structPtr, 0, (uint)size);
- }
+ /// <param name="block">The pointer to the allocated structure</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ZeroStruct<T>(IntPtr block) => ZeroStruct<T>(block.ToPointer());
/// <summary>
/// Zeroes a block of memory pointing to the structure
@@ -355,12 +343,20 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">The structure type</typeparam>
/// <param name="structPtr">The pointer to the allocated structure</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ZeroStruct<T>(T* structPtr) where T : unmanaged => Unsafe.InitBlock(structPtr, 0, (uint)sizeof(T));
+ public static void ZeroStruct<T>(T* structPtr) where T : unmanaged => ZeroStruct<T>((void*)structPtr);
#endregion
#region Copy
+ /*
+ * Dirty little trick to access internal Buffer.Memmove method for
+ * large references. May not always be supported, so optional safe
+ * guards are in place.
+ */
+ private delegate void BigMemmove(ref byte dest, ref byte src, nuint len);
+ private static readonly BigMemmove? _sysMemmove = ManagedLibrary.TryGetStaticMethod<BigMemmove>(typeof(Buffer), "Memmove", System.Reflection.BindingFlags.NonPublic);
+
/// <summary>
/// Copies data from source memory to destination memory of an umanged data type
/// </summary>
@@ -369,7 +365,7 @@ namespace VNLib.Utils.Memory
/// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param>
/// <param name="destOffset">Dest offset</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(ReadOnlySpan<T> source, MemoryHandle<T> dest, nuint destOffset) where T : unmanaged
+ public static void Copy<T>(ReadOnlySpan<T> source, IMemoryHandle<T> dest, nuint destOffset) where T: struct
{
if (dest is null)
{
@@ -381,11 +377,17 @@ namespace VNLib.Utils.Memory
return;
}
- //Get long offset from the destination handle (also checks bounds)
- Span<T> dst = dest.GetOffsetSpan(destOffset, source.Length);
+ //Check memhandle bounds
+ CheckBounds(dest, destOffset, (uint)source.Length);
- //Copy data
- source.CopyTo(dst);
+ //Get byte ref and byte count
+ nuint byteCount = ByteCount<T>((uint)source.Length);
+ ref T src = ref MemoryMarshal.GetReference(source);
+ ref T dst = ref dest.GetReference();
+
+ //Use memmove by ref
+ bool success = MemmoveByRef(ref src, 0, ref dst, (uint)destOffset, byteCount);
+ Debug.Assert(success, "Memmove by ref call failed during a 32bit copy");
}
/// <summary>
@@ -396,24 +398,7 @@ namespace VNLib.Utils.Memory
/// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param>
/// <param name="destOffset">Dest offset</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(ReadOnlyMemory<T> source, MemoryHandle<T> dest, nuint destOffset) where T : unmanaged
- {
- if (dest is null)
- {
- throw new ArgumentNullException(nameof(dest));
- }
-
- if (source.IsEmpty)
- {
- return;
- }
-
- //Get long offset from the destination handle (also checks bounds)
- Span<T> dst = dest.GetOffsetSpan(destOffset, source.Length);
-
- //Copy data
- source.Span.CopyTo(dst);
- }
+ public static void Copy<T>(ReadOnlyMemory<T> source, IMemoryHandle<T> dest, nuint destOffset) where T : struct => Copy(source.Span, dest, destOffset);
/// <summary>
/// Copies data from source memory to destination memory of an umanged data type
@@ -425,10 +410,12 @@ namespace VNLib.Utils.Memory
/// <param name="destOffset">Dest offset</param>
/// <param name="count">Number of elements to copy</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(MemoryHandle<T> source, nint sourceOffset, Span<T> dest, int destOffset, int count) where T : unmanaged
+ public static void Copy<T>(IMemoryHandle<T> source, nint sourceOffset, Span<T> dest, int destOffset, int count) where T : struct
{
+ _ = source ?? throw new ArgumentNullException(nameof(source));
+
//Validate source/dest/count
- ValidateArgs(sourceOffset, destOffset, count);
+ ValidateCopyArgs(sourceOffset, destOffset, count);
//Check count last for debug reasons
if (count == 0)
@@ -436,14 +423,17 @@ namespace VNLib.Utils.Memory
return;
}
- //Get offset span, also checks bounts
- Span<T> src = source.GetOffsetSpan(sourceOffset, count);
-
- //slice the dest span
- Span<T> dst = dest.Slice(destOffset, count);
+ //Check source bounds
+ CheckBounds(source, (nuint)sourceOffset, (nuint)count);
- //Copy data
- src.CopyTo(dst);
+ //Get byte ref and byte count
+ nuint byteCount = ByteCount<T>((uint)count);
+ ref T src = ref source.GetReference();
+ ref T dst = ref MemoryMarshal.GetReference(dest);
+
+ //Use memmove by ref
+ bool success = MemmoveByRef(ref src, (uint)sourceOffset, ref dst, (uint)destOffset, byteCount);
+ Debug.Assert(success, "Memmove by ref call failed during a 32bit copy");
}
/// <summary>
@@ -458,13 +448,51 @@ namespace VNLib.Utils.Memory
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Copy<T>(MemoryHandle<T> source, nint sourceOffset, Memory<T> dest, int destOffset, int count) where T : unmanaged
+ public static void Copy<T>(IMemoryHandle<T> source, nint sourceOffset, Memory<T> dest, int destOffset, int count) where T : struct
+ => Copy(source, sourceOffset, dest.Span, destOffset, count);
+
+ /// <summary>
+ /// Copies data from source memory to destination memory of an umanged data type
+ /// using references for blocks smaller than <see cref="UInt32.MaxValue"/> and
+ /// pinning for larger blocks
+ /// </summary>
+ /// <typeparam name="T">Unmanged type</typeparam>
+ /// <param name="source">Source data <see cref="MemoryHandle{T}"/></param>
+ /// <param name="sourceOffset">Number of elements to offset source data</param>
+ /// <param name="dest">Destination <see cref="Memory{T}"/></param>
+ /// <param name="destOffset">Dest offset</param>
+ /// <param name="count">Number of elements to copy</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void Copy<T>(IMemoryHandle<T> source, nuint sourceOffset, IMemoryHandle<T> dest, nuint destOffset, nuint count) where T : unmanaged
{
- //Call copy method with dest as span
- Copy(source, sourceOffset, dest.Span, destOffset, count);
+ _ = source ?? throw new ArgumentNullException(nameof(source));
+ _ = dest ?? throw new ArgumentNullException(nameof(dest));
+
+ CheckBounds(source, sourceOffset, count);
+ CheckBounds(dest, destOffset, count);
+
+ //Get byte ref and byte count
+ nuint byteCount = ByteCount<T>(count);
+ ref T src = ref source.GetReference();
+ ref T dst = ref dest.GetReference();
+
+ if (!MemmoveByRef(ref src, sourceOffset, ref dst, destOffset, byteCount))
+ {
+ //Copying block larger than 32bit must be done with pointers
+ using MemoryHandle srcH = source.Pin(0);
+ using MemoryHandle dstH = dest.Pin(0);
+
+ //Get pointers and add offsets
+ T* srcOffset = ((T*)srcH.Pointer) + sourceOffset;
+ T* dstOffset = ((T*)dstH.Pointer) + destOffset;
+
+ //Copy memory
+ Buffer.MemoryCopy(srcOffset, dstOffset, byteCount, byteCount);
+ }
}
- private static void ValidateArgs(nint sourceOffset, nint destOffset, nint count)
+ private static void ValidateCopyArgs(nint sourceOffset, nint destOffset, nint count)
{
if(sourceOffset < 0)
{
@@ -483,17 +511,19 @@ namespace VNLib.Utils.Memory
}
/// <summary>
- /// 32/64 bit large block copy
+ /// Preforms a fast referrence based copy on very large blocks of memory
+ /// using pinning and pointers only when the number of bytes to copy is
+ /// larger than <see cref="UInt32.MaxValue"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source memory handle to copy data from</param>
- /// <param name="offset">The element offset to begin reading from</param>
+ /// <param name="sourceOffset">The element offset to begin reading from</param>
/// <param name="dest">The destination array to write data to</param>
/// <param name="destOffset"></param>
/// <param name="count">The number of elements to copy</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(IMemoryHandle<T> source, nuint offset, T[] dest, nuint destOffset, nuint count) where T : unmanaged
+ public static void Copy<T>(IMemoryHandle<T> source, nuint sourceOffset, T[] dest, nuint destOffset, nuint count) where T : unmanaged
{
if (source is null)
{
@@ -511,41 +541,62 @@ namespace VNLib.Utils.Memory
}
//Check source bounds
- CheckBounds(source, offset, count);
+ CheckBounds(source, sourceOffset, count);
- //Check dest bounts
+ //Check dest bounds
CheckBounds(dest, destOffset, count);
- //Check if 64bit
- if(sizeof(void*) == 8)
+ //Get byte refs and byte count
+ nuint byteCount = ByteCount<T>(count);
+ ref T src = ref source.GetReference();
+ ref T dst = ref MemoryMarshal.GetArrayDataReference(dest);
+
+ //Try to memove by ref first, otherwise fallback to pinning
+ if (!MemmoveByRef(ref src, sourceOffset, ref dst, destOffset, byteCount))
{
- //Get the number of bytes to copy
- nuint byteCount = ByteCount<T>(count);
+ //Copying block larger than 32bit must be done with pointers
+ using MemoryHandle srcH = source.Pin(0);
+ using MemoryHandle dstH = PinArrayAndGetHandle(dest, 0);
- //Get memory handle from source
- using MemoryHandle srcHandle = source.Pin(0);
+ //Get pointers and add offsets
+ T* srcOffset = ((T*)srcH.Pointer) + sourceOffset;
+ T* dstOffset = ((T*)dstH.Pointer) + destOffset;
- //get source offset
- T* src = (T*)srcHandle.Pointer + offset;
+ //Copy memory
+ Buffer.MemoryCopy(srcOffset, dstOffset, byteCount, byteCount);
+ }
+ }
+
- //pin array
- fixed (T* dst = &MemoryMarshal.GetArrayDataReference(dest))
- {
- //Offset dest ptr
- T* dstOffset = dst + destOffset;
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private static bool MemmoveByRef<T>(ref T src, nuint srcOffset, ref T dst, nuint dstOffset, nuint byteCount) where T : struct
+ {
+ Debug.Assert(!Unsafe.IsNullRef(ref src), "Null source reference passed to MemmoveByRef");
+ Debug.Assert(!Unsafe.IsNullRef(ref dst), "Null destination reference passed to MemmoveByRef");
- //Copy src to set
- Buffer.MemoryCopy(src, dstOffset, byteCount, byteCount);
- }
+ //Get offset referrences to the source and destination
+ ref T srcOffsetPtr = ref Unsafe.Add(ref src, srcOffset);
+ ref T dstOffsetPtr = ref Unsafe.Add(ref dst, dstOffset);
+
+ //Cast to byte pointers
+ ref byte srcByte = ref Unsafe.As<T, byte>(ref srcOffsetPtr);
+ ref byte dstByte = ref Unsafe.As<T, byte>(ref dstOffsetPtr);
+
+ if (_sysMemmove != null)
+ {
+ //Call sysinternal memmove
+ _sysMemmove(ref dstByte, ref srcByte, byteCount);
+ return true;
+ }
+ else if(byteCount < uint.MaxValue)
+ {
+ //Use safe 32bit block copy
+ Unsafe.CopyBlock(ref dstByte, ref srcByte, (uint)byteCount);
+ return true;
}
else
{
- //If 32bit its safe to use spans
-
- Span<T> src = source.AsSpan((int)offset, (int)count);
- Span<T> dst = dest.AsSpan((int)destOffset, (int)count);
- //Copy
- src.CopyTo(dst);
+ return false;
}
}
@@ -598,6 +649,26 @@ namespace VNLib.Utils.Memory
public static uint ByteCount<T>(uint elementCount) => checked(elementCount * (uint)Unsafe.SizeOf<T>());
/// <summary>
+ /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values
+ /// </summary>
+ /// <typeparam name="T">The type to get the byte offset of</typeparam>
+ /// <param name="elementCount">The number of elements to get the byte count of</param>
+ /// <returns>The byte multiple of the number of elments</returns>
+ /// <exception cref="OverflowException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint ByteCount<T>(nint elementCount) => checked(elementCount * Unsafe.SizeOf<T>());
+
+ /// <summary>
+ /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values
+ /// </summary>
+ /// <typeparam name="T">The type to get the byte offset of</typeparam>
+ /// <param name="elementCount">The number of elements to get the byte count of</param>
+ /// <returns>The byte multiple of the number of elments</returns>
+ /// <exception cref="OverflowException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ByteCount<T>(int elementCount) => checked(elementCount * Unsafe.SizeOf<T>());
+
+ /// <summary>
/// Checks if the offset/count paramters for the given memory handle
/// point outside the block wrapped in the handle
/// </summary>
@@ -682,21 +753,21 @@ namespace VNLib.Utils.Memory
throw new ArgumentOutOfRangeException(nameof(elementOffset));
}
+ _ = array ?? throw new ArgumentNullException(nameof(array));
+
//Quick verify index exists, may be the very last index
CheckBounds(array, (nuint)elementOffset, 1);
//Pin the array
GCHandle arrHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
- //Get array base address
- void* basePtr = (void*)arrHandle.AddrOfPinnedObject();
-
- Debug.Assert(basePtr != null);
+ //safe to get array basee pointer
+ ref T arrBase = ref MemoryMarshal.GetArrayDataReference(array);
//Get element offset
- void* indexOffet = Unsafe.Add<T>(basePtr, elementOffset);
+ ref T indexOffet = ref Unsafe.Add(ref arrBase, elementOffset);
- return new(indexOffet, arrHandle);
+ return new(Unsafe.AsPointer(ref indexOffet), arrHandle);
}
/// <summary>
@@ -741,6 +812,29 @@ namespace VNLib.Utils.Memory
public static Span<T> GetSpan<T>(MemoryHandle handle, int size) => new(handle.Pointer, size);
/// <summary>
+ /// Recovers a reference to the supplied pointer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="address">The base address to cast to a reference</param>
+ /// <returns>The reference to the supplied address</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetRef<T>(IntPtr address) => ref Unsafe.AsRef<T>(address.ToPointer());
+
+ /// <summary>
+ /// Recovers a reference to the supplied pointer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="address">The base address to cast to a reference</param>
+ /// <param name="offset">The offset to add to the base address</param>
+ /// <returns>The reference to the supplied address</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T GetRef<T>(IntPtr address, nuint offset)
+ {
+ ref T baseRef = ref GetRef<T>(address);
+ return ref Unsafe.Add(ref baseRef, (nint)offset);
+ }
+
+ /// <summary>
/// Rounds the requested byte size up to the nearest page
/// number of bytes
/// </summary>
diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
index e4210e7..9e305d0 100644
--- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs
+++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
@@ -92,7 +92,7 @@ namespace VNLib.Utils.Memory
}
//Round to nearest page (in bytes)
- nint np = NearestPage(elements * sizeof(T));
+ nint np = NearestPage(ByteCount<T>(elements));
//Resize to element size
np /= sizeof(T);
@@ -131,7 +131,7 @@ namespace VNLib.Utils.Memory
}
else
{
- return new VnTempBuffer<T>(ArrayPool<T>.Shared, elements, zero);
+ return new ArrayPoolBuffer<T>(ArrayPool<T>.Shared, elements, zero);
}
}
@@ -154,7 +154,7 @@ namespace VNLib.Utils.Memory
}
//Round to nearest page (in bytes)
- nint np = NearestPage(elements * sizeof(T));
+ nint np = NearestPage(ByteCount<T>(elements));
//Resize to element size
np /= sizeof(T);
@@ -258,7 +258,7 @@ namespace VNLib.Utils.Memory
}
else
{
- return new VnTempBuffer<byte>(ArrayPool<byte>.Shared, elements, zero);
+ return new ArrayPoolBuffer<byte>(ArrayPool<byte>.Shared, elements, zero);
}
}
diff --git a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs
index f2bbd51..a17a906 100644
--- a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs
+++ b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs
@@ -25,6 +25,8 @@
using System;
using System.Buffers;
+using VNLib.Utils.Extensions;
+
namespace VNLib.Utils.Memory
{
/// <summary>
@@ -48,7 +50,7 @@ namespace VNLib.Utils.Memory
///<exception cref="OutOfMemoryException"></exception>
///<exception cref="ObjectDisposedException"></exception>
///<exception cref="ArgumentOutOfRangeException"></exception>
- public override IMemoryOwner<T> Rent(int minBufferSize = 0) => new SysBufferMemoryManager<T>(Heap, (uint)minBufferSize, false);
+ public override IMemoryOwner<T> Rent(int minBufferSize = 0) => Heap.DirectAlloc<T>(minBufferSize, false);
/// <summary>
/// Allocates a new <see cref="MemoryManager{T}"/> of a different data type from the pool
@@ -56,7 +58,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="TDifType">The unmanaged data type to allocate for</typeparam>
/// <param name="minBufferSize">Minumum size of the buffer</param>
/// <returns>The memory owner of a different data type</returns>
- public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged => new SysBufferMemoryManager<TDifType>(Heap, (uint)minBufferSize, false);
+ public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged => Heap.DirectAlloc<TDifType>(minBufferSize, false);
///<inheritdoc/>
protected override void Dispose(bool disposing)
diff --git a/lib/Utils/src/Memory/ProcessHeap.cs b/lib/Utils/src/Memory/ProcessHeap.cs
index 3d581cd..5d1bee6 100644
--- a/lib/Utils/src/Memory/ProcessHeap.cs
+++ b/lib/Utils/src/Memory/ProcessHeap.cs
@@ -49,7 +49,7 @@ namespace VNLib.Utils.Memory
/// process heap. Meaining memory will be shared across the process
/// </para>
/// </summary>
- public HeapCreation CreationFlags { get; } = HeapCreation.Shared;
+ public HeapCreation CreationFlags { get; } = HeapCreation.Shared | HeapCreation.SupportsRealloc;
/// <summary>
/// Initalizes a new global (cross platform) process heap
diff --git a/lib/Utils/src/Memory/SubSequence.cs b/lib/Utils/src/Memory/SubSequence.cs
index 1db0ba5..86b2347 100644
--- a/lib/Utils/src/Memory/SubSequence.cs
+++ b/lib/Utils/src/Memory/SubSequence.cs
@@ -32,14 +32,14 @@ namespace VNLib.Utils.Memory
/// Represents a subset (or window) of data within a <see cref="MemoryHandle{T}"/>
/// </summary>
/// <typeparam name="T">The unmanaged type to wrap</typeparam>
- public readonly record struct SubSequence<T> where T: unmanaged
+ public readonly record struct SubSequence<T>
{
readonly nuint _offset;
/// <summary>
/// The handle that owns the memory block
/// </summary>
- public readonly MemoryHandle<T> Handle { get; }
+ public readonly IMemoryHandle<T> Handle { get; }
/// <summary>
/// The number of elements in the current sequence
@@ -54,7 +54,7 @@ namespace VNLib.Utils.Memory
/// <param name="size"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public SubSequence(MemoryHandle<T> block, nuint offset, int size)
+ public SubSequence(IMemoryHandle<T> block, nuint offset, int size)
{
Handle = block ?? throw new ArgumentNullException(nameof(block));
Size = size >= 0 ? size : throw new ArgumentOutOfRangeException(nameof(size));
diff --git a/lib/Utils/src/Memory/SysBufferMemoryManager.cs b/lib/Utils/src/Memory/SysBufferMemoryManager.cs
index aca2543..26c3688 100644
--- a/lib/Utils/src/Memory/SysBufferMemoryManager.cs
+++ b/lib/Utils/src/Memory/SysBufferMemoryManager.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Utils
@@ -25,8 +25,6 @@
using System;
using System.Buffers;
-using VNLib.Utils.Extensions;
-
namespace VNLib.Utils.Memory
{
/// <summary>
@@ -34,7 +32,7 @@ namespace VNLib.Utils.Memory
/// as a memory provider which implements a <see cref="System.Runtime.InteropServices.SafeHandle"/>
/// </summary>
/// <typeparam name="T">Unmanaged memory type</typeparam>
- public sealed class SysBufferMemoryManager<T> : MemoryManager<T> where T :unmanaged
+ public sealed class SysBufferMemoryManager<T> : MemoryManager<T>
{
private readonly IMemoryHandle<T> BackingMemory;
private readonly bool _ownsHandle;
@@ -45,30 +43,19 @@ namespace VNLib.Utils.Memory
/// </summary>
/// <param name="existingHandle">The existing handle to consume</param>
/// <param name="ownsHandle">A value that indicates if the memory manager owns the handle reference</param>
- internal SysBufferMemoryManager(IMemoryHandle<T> existingHandle, bool ownsHandle)
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="OverflowException"></exception>
+ public SysBufferMemoryManager(IMemoryHandle<T> existingHandle, bool ownsHandle)
{
- BackingMemory = existingHandle;
- _ownsHandle = ownsHandle;
- }
-
- /// <summary>
- /// Allocates a fized size buffer from the specified unmanaged <see cref="Win32PrivateHeap"/>
- /// </summary>
- /// <param name="heap">The heap to perform allocations from</param>
- /// <param name="elements">The number of elements to allocate</param>
- /// <param name="zero">Zero allocations</param>
- public SysBufferMemoryManager(IUnmangedHeap heap, nuint elements, bool zero)
- {
- BackingMemory = heap.Alloc<T>(elements, zero);
- _ownsHandle = true;
- }
+ BackingMemory = existingHandle ?? throw new ArgumentNullException(nameof(existingHandle));
+
+ //check for overflow
+ if(existingHandle.Length > Int32.MaxValue)
+ {
+ throw new OverflowException("This memory manager does not accept handles larger than Int32.MaxValue");
+ }
- ///<inheritdoc/>
- protected override bool TryGetArray(out ArraySegment<T> segment)
- {
- //Always false since no array is available
- segment = default;
- return false;
+ _ownsHandle = ownsHandle;
}
///<inheritdoc/>
@@ -81,11 +68,8 @@ namespace VNLib.Utils.Memory
/// </summary>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public unsafe override MemoryHandle Pin(int elementIndex = 0)
- {
- return BackingMemory.Pin(elementIndex);
- }
-
+ public unsafe override MemoryHandle Pin(int elementIndex = 0) => BackingMemory.Pin(elementIndex);
+
///<inheritdoc/>
public override void Unpin()
{}
diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs
index bfa6736..0310582 100644
--- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs
+++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs
@@ -39,21 +39,12 @@ namespace VNLib.Utils.Memory
/// </summary>
public abstract class UnmanagedHeapBase : SafeHandleZeroOrMinusOneIsInvalid, IUnmangedHeap
{
+ private readonly HeapCreation _flags;
+
/// <summary>
/// The heap synchronization handle
/// </summary>
protected readonly object HeapLock;
-
- /// <summary>
- /// The global heap zero flag
- /// </summary>
- protected readonly bool GlobalZero;
-
- /// <summary>
- /// A value that inidicates that locking will
- /// be used when invoking heap operations
- /// </summary>
- protected readonly bool UseSynchronization;
/// <summary>
/// Initalizes the unmanaged heap base class (init synchronization handle)
@@ -63,13 +54,11 @@ namespace VNLib.Utils.Memory
protected UnmanagedHeapBase(HeapCreation flags, bool ownsHandle) : base(ownsHandle)
{
HeapLock = new();
- GlobalZero = flags.HasFlag(HeapCreation.GlobalZero);
- UseSynchronization = flags.HasFlag(HeapCreation.UseSynchronization);
- CreationFlags = flags;
+ _flags = flags;
}
///<inheritdoc/>
- public HeapCreation CreationFlags { get; }
+ public HeapCreation CreationFlags => _flags;
///<inheritdoc/>
///<remarks>Increments the handle count, free must be called to decrement the handle count</remarks>
@@ -82,7 +71,7 @@ namespace VNLib.Utils.Memory
_ = checked(elements * size);
//Force zero if global flag is set
- zero |= GlobalZero;
+ zero |= (_flags & HeapCreation.GlobalZero) > 0;
bool handleCountIncremented = false;
//Increment handle count to prevent premature release
@@ -99,7 +88,7 @@ namespace VNLib.Utils.Memory
LPVOID block;
//Check if lock should be used
- if (UseSynchronization)
+ if ((_flags & HeapCreation.UseSynchronization) > 0)
{
//Enter lock
lock(HeapLock)
@@ -138,7 +127,7 @@ namespace VNLib.Utils.Memory
return true;
}
- if (UseSynchronization)
+ if ((_flags & HeapCreation.UseSynchronization) > 0)
{
//wait for lock
lock (HeapLock)
@@ -173,7 +162,7 @@ namespace VNLib.Utils.Memory
LPVOID newBlock;
//Global zero flag will cause a zero
- zero |= GlobalZero;
+ zero |= (_flags & HeapCreation.GlobalZero) > 0;
/*
* Realloc may return a null pointer if allocation fails
@@ -182,7 +171,7 @@ namespace VNLib.Utils.Memory
* be left untouched
*/
- if (UseSynchronization)
+ if ((_flags & HeapCreation.UseSynchronization) > 0)
{
lock (HeapLock)
{
diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs
index 6d566f1..e4857d1 100644
--- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs
+++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs
@@ -165,8 +165,7 @@ namespace VNLib.Utils.Memory
if (elementIndex < 0 || elementIndex >= IntLength)
{
throw new ArgumentOutOfRangeException(nameof(elementIndex));
- }
-
+ }
if (_handleType == HandleType.Pool)
{
@@ -174,10 +173,11 @@ namespace VNLib.Utils.Memory
}
else
{
- //Get offset pointer and pass self as pinnable argument, (nothing happens but support it)
- void* basePtr = Unsafe.Add<T>(_memoryPtr.ToPointer(), elementIndex);
+ //Add an offset to the base address of the memory block
+ int byteOffset = MemoryUtil.ByteCount<T>(elementIndex);
+ IntPtr offset = IntPtr.Add(_memoryPtr, byteOffset);
//Unmanaged memory is always pinned, so no need to pass this as IPinnable, since it will cause a box
- return new (basePtr);
+ return MemoryUtil.GetMemoryHandleFromPointer(offset);
}
}
///<inheritdoc/>
@@ -186,6 +186,20 @@ namespace VNLib.Utils.Memory
//Nothing to do since gc handle takes care of array, and unmanaged pointers are not pinned
}
+ ///<inheritdoc/>
+ public readonly ref T GetReference()
+ {
+ switch (_handleType)
+ {
+ case HandleType.Pool:
+ return ref MemoryMarshal.GetArrayDataReference(_poolArr!);
+ case HandleType.PrivateHeap:
+ return ref MemoryUtil.GetRef<T>(_memoryPtr);
+ default:
+ throw new InvalidOperationException("The handle is empty, and cannot capture a reference");
+ }
+ }
+
/// <summary>
/// Determines if the other handle represents the same memory block as the
/// current handle.
diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs
index 8542688..c937ccc 100644
--- a/lib/Utils/src/Memory/VnString.cs
+++ b/lib/Utils/src/Memory/VnString.cs
@@ -44,7 +44,7 @@ namespace VNLib.Utils.Memory
[ImmutableObject(true)]
public sealed class VnString : VnDisposeable, IEquatable<VnString>, IEquatable<string>, IEquatable<char[]>, IComparable<VnString>, IComparable<string>
{
- private readonly MemoryHandle<char>? Handle;
+ private readonly IMemoryHandle<char>? Handle;
private readonly SubSequence<char> _stringSequence;
@@ -63,7 +63,7 @@ namespace VNLib.Utils.Memory
_stringSequence = sequence;
}
- private VnString(MemoryHandle<char> handle, nuint start, int length)
+ private VnString(IMemoryHandle<char> handle, nuint start, int length)
{
Handle = handle ?? throw new ArgumentNullException(nameof(handle));
//get sequence
@@ -517,14 +517,10 @@ namespace VNLib.Utils.Memory
/// </remarks>
/// <exception cref="ObjectDisposedException"></exception>
public int GetHashCode(StringComparison stringComparison) => string.GetHashCode(AsSpan(), stringComparison);
-
+
///<inheritdoc/>
- protected override void Free()
- {
- //Dispose the handle if we own it (null if we do not have the parent handle)
- Handle?.Dispose();
- }
-
+ protected override void Free() => Handle?.Dispose();
+
public static bool operator ==(VnString left, VnString right) => left is null ? right is not null : left.Equals(right, StringComparison.Ordinal);
public static bool operator !=(VnString left, VnString right) => !(left == right);
diff --git a/lib/Utils/src/Memory/Win32PrivateHeap.cs b/lib/Utils/src/Memory/Win32PrivateHeap.cs
index 41fe33a..42f0328 100644
--- a/lib/Utils/src/Memory/Win32PrivateHeap.cs
+++ b/lib/Utils/src/Memory/Win32PrivateHeap.cs
@@ -177,8 +177,7 @@ namespace VNLib.Utils.Memory
//validate the block on the current heap
result = HeapValidate(handle, HEAP_NO_FLAGS, block);
}
- return result;
-
+ return result;
}
/// <summary>
diff --git a/lib/Utils/src/Resources/ManagedLibrary.cs b/lib/Utils/src/Resources/ManagedLibrary.cs
index f9813a1..56835c7 100644
--- a/lib/Utils/src/Resources/ManagedLibrary.cs
+++ b/lib/Utils/src/Resources/ManagedLibrary.cs
@@ -28,6 +28,7 @@ using System.Linq;
using System.Threading;
using System.Reflection;
using System.Runtime.Loader;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
using VNLib.Utils.IO;
@@ -76,11 +77,8 @@ namespace VNLib.Utils.Resources
_lazyAssembly = new(LoadAssembly, LazyThreadSafetyMode.PublicationOnly);
}
- private Assembly LoadAssembly()
- {
- //Load the assembly into the parent context
- return _loadContext.LoadFromAssemblyPath(AssemblyPath);
- }
+ //Load the assembly into the parent context
+ private Assembly LoadAssembly() => _loadContext.LoadFromAssemblyPath(AssemblyPath);
/// <summary>
/// Raised when the load context that owns this assembly
@@ -136,18 +134,50 @@ namespace VNLib.Utils.Resources
/// <exception cref="EntryPointNotFoundException"></exception>
public T LoadTypeFromAssembly<T>()
{
- Type resourceType = typeof(T);
-
//See if the type is exported
- Type exp = (from type in Assembly.GetExportedTypes()
- where resourceType.IsAssignableFrom(type)
- select type)
- .FirstOrDefault()
- ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {resourceType.FullName}");
+ Type exp = TryGetExportedType<T>() ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {typeof(T).FullName}");
//Create instance
return (T)Activator.CreateInstance(exp)!;
- }
+ }
+
+ /// <summary>
+ /// Gets the type exported from the current assembly that is
+ /// assignable to the desired type.
+ /// </summary>
+ /// <typeparam name="T">The desired base type to get the exported type of</typeparam>
+ /// <returns>The exported type that matches the desired type from the current assembly</returns>
+ public Type? TryGetExportedType<T>() => TryGetExportedType(typeof(T));
+
+ /// <summary>
+ /// Gets the type exported from the current assembly that is
+ /// assignable to the desired type.
+ /// </summary>
+ /// <param name="resourceType">The desired base type to get the exported type of</param>
+ /// <returns>The exported type that matches the desired type from the current assembly</returns>
+ public Type? TryGetExportedType(Type resourceType) => TryGetAllMatchingTypes(resourceType).FirstOrDefault();
+
+ /// <summary>
+ /// Gets all exported types from the current assembly that are
+ /// assignable to the desired type.
+ /// </summary>
+ /// <typeparam name="T">The desired resource type</typeparam>
+ /// <returns>An enumeration of acceptable types</returns>
+ public IEnumerable<Type> TryGetAllMatchingTypes<T>() => TryGetAllMatchingTypes(typeof(T));
+
+ /// <summary>
+ /// Gets all exported types from the current assembly that are
+ /// assignable to the desired type.
+ /// </summary>
+ /// <param name="resourceType">The desired resource type</param>
+ /// <returns>An enumeration of acceptable types</returns>
+ public IEnumerable<Type> TryGetAllMatchingTypes(Type resourceType)
+ {
+ //try to get all exported types that match the desired type
+ return from type in Assembly.GetExportedTypes()
+ where resourceType.IsAssignableFrom(type)
+ select type;
+ }
/// <summary>
/// Creates a new loader for the desired assembly. The assembly and its dependencies
@@ -171,5 +201,87 @@ namespace VNLib.Utils.Resources
FileInfo fi = new(assemblyName);
return new(fi.FullName, loadContext);
}
+
+ /// <summary>
+ /// A helper method that will attempt to get a named method of the desired
+ /// delegate type from the specified object.
+ /// </summary>
+ /// <typeparam name="TDelegate">The method delegate that matches the signature of the desired method</typeparam>
+ /// <param name="obj">The object to discover and bind the found method to</param>
+ /// <param name="methodName">The name of the method to capture</param>
+ /// <param name="flags">The method binding flags</param>
+ /// <returns>The namaed method delegate for the object type, or null if the method was not found</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static TDelegate? TryGetMethod<TDelegate>(
+ object obj,
+ string methodName,
+ BindingFlags flags = BindingFlags.Public
+ ) where TDelegate : Delegate
+ {
+ _ = obj ?? throw new ArgumentNullException(nameof(obj));
+ return TryGetMethodInternal<TDelegate>(obj.GetType(), methodName, obj, flags | BindingFlags.Instance);
+ }
+
+ /// <summary>
+ /// A helper method that will attempt to get a named method of the desired
+ /// delegate type from the specified object.
+ /// </summary>
+ /// <typeparam name="TDelegate">The method delegate that matches the signature of the desired method</typeparam>
+ /// <param name="obj">The object to discover and bind the found method to</param>
+ /// <param name="methodName">The name of the method to capture</param>
+ /// <param name="flags">The method binding flags</param>
+ /// <returns>The namaed method delegate for the object type or an exception if not found</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMethodException"></exception>
+ public static TDelegate GetMethod<TDelegate>(
+ object obj,
+ string methodName,
+ BindingFlags flags = BindingFlags.Public
+ ) where TDelegate : Delegate
+ {
+ return TryGetMethod<TDelegate>(obj, methodName, flags)
+ ?? throw new MissingMethodException($"Type {obj.GetType().FullName} is missing desired method {methodName}");
+ }
+
+ /// <summary>
+ /// A helper method that will attempt to get a named static method of the desired
+ /// delegate type from the specified type.
+ /// </summary>
+ /// <typeparam name="TDelegate"></typeparam>
+ /// <param name="type">The type to get the static method for</param>
+ /// <param name="methodName">The name of the static method</param>
+ /// <param name="flags">The optional method binind flags</param>
+ /// <returns>The delegate if found <see langword="null"/> otherwise</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static TDelegate? TryGetStaticMethod<TDelegate>(Type type, string methodName, BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate
+ => TryGetMethodInternal<TDelegate>(type, methodName, null, flags | BindingFlags.Static);
+
+ /// <summary>
+ /// A helper method that will attempt to get a named static method of the desired
+ /// delegate type from the specified type.
+ /// </summary>
+ /// <typeparam name="TDelegate">The delegate method type</typeparam>
+ /// <typeparam name="TType">The type to get the static method for</typeparam>
+ /// <param name="methodName">The name of the static method</param>
+ /// <param name="flags">The optional method binind flags</param>
+ /// <returns>The delegate if found <see langword="null"/> otherwise</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static TDelegate? TryGetStaticMethod<TDelegate, TType>(string methodName,BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate
+ => TryGetMethodInternal<TDelegate>(typeof(TType), methodName, null, flags | BindingFlags.Static);
+
+ private static TDelegate? TryGetMethodInternal<TDelegate>(Type type, string methodName, object? target, BindingFlags flags) where TDelegate : Delegate
+ {
+ _ = type ?? throw new ArgumentNullException(nameof(type));
+
+ //Get delegate argument types incase of a method overload
+ Type[] delegateArgs = typeof(TDelegate).GetMethod("Invoke")!
+ .GetParameters()
+ .Select(static p => p.ParameterType)
+ .ToArray();
+
+ //get the named method and always add the static flag
+ return type.GetMethod(methodName, flags, delegateArgs)
+ ?.CreateDelegate<TDelegate>(target);
+ }
}
}
diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs
index 212eb0c..8880010 100644
--- a/lib/Utils/tests/Memory/MemoryHandleTest.cs
+++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs
@@ -23,6 +23,7 @@
*/
using System;
+using System.Runtime.CompilerServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -197,6 +198,8 @@ namespace VNLib.Utils.Memory.Tests
//Pin should throw
Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = thandle.Pin(0));
+
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = thandle.GetReference());
}
//Full ref to mhandle check status
@@ -217,6 +220,8 @@ namespace VNLib.Utils.Memory.Tests
Assert.ThrowsException<ObjectDisposedException>(() => mHandle.Resize(10));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => mHandle.BasePtr);
+
+ Assert.ThrowsException<ObjectDisposedException>(() => _ = mHandle.GetReference());
}
}
}
diff --git a/lib/Utils/tests/VNLib.UtilsTests.csproj b/lib/Utils/tests/VNLib.UtilsTests.csproj
index c29915d..9053c51 100644
--- a/lib/Utils/tests/VNLib.UtilsTests.csproj
+++ b/lib/Utils/tests/VNLib.UtilsTests.csproj
@@ -16,7 +16,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0">