aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-27 21:13:05 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-27 21:13:05 -0500
commita5d88f2cf08ea3aad2c8802bdc416e7b40c0f204 (patch)
treee7b9347717c096ef0ea517769b5136ef8abf9571 /lib
parent185afcee727027c60257ddda4da974dccb808e5a (diff)
Object cache overhaul and logger updates
Diffstat (limited to 'lib')
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs30
-rw-r--r--lib/Net.Http/src/Core/Response/ChunkedStream.cs2
-rw-r--r--lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs5
-rw-r--r--lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs9
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs169
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClient.cs17
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs5
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMRequest.cs180
-rw-r--r--lib/Net.Messaging.FBM/src/Client/FBMResponse.cs6
-rw-r--r--lib/Net.Messaging.FBM/src/Client/Helpers.cs138
-rw-r--r--lib/Net.Messaging.FBM/src/Client/README.md5
-rw-r--r--lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs60
-rw-r--r--lib/Net.Messaging.FBM/src/FBMMessageHeader.cs94
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMContext.cs13
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListener.cs7
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs2
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs60
-rw-r--r--lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs59
-rw-r--r--lib/Net.Messaging.FBM/src/Server/readme.md2
-rw-r--r--lib/Plugins.Essentials/src/HttpEntity.cs1
-rw-r--r--lib/Plugins.Essentials/src/Sessions/SessionInfo.cs2
-rw-r--r--lib/Plugins.PluginBase/src/VLogProvider.cs13
-rw-r--r--lib/Utils/src/Logging/ILogProvider.cs8
-rw-r--r--lib/Utils/src/Memory/Caching/LRUCache.cs13
-rw-r--r--lib/Utils/src/Memory/Caching/LRUDataStore.cs43
-rw-r--r--lib/Utils/src/Memory/MemoryHandle.cs2
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs33
-rw-r--r--lib/Utils/tests/VNLib.UtilsTests.csproj6
28 files changed, 701 insertions, 283 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs
index e3822d0..55e31fc 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs
@@ -47,24 +47,31 @@ namespace VNLib.Hashing.IdentityUtility
/// <summary>
/// Parses a JWT from a Base64URL encoded character buffer
/// </summary>
- /// <param name="urlEncJwtString"></param>
+ /// <param name="urlEncJwtString">The JWT characters to decode</param>
/// <param name="heap">An optional <see cref="IUnmangedHeap"/> instance to alloc buffers from</param>
+ /// <param name="textEncoding">The encoding used to decode the text to binary</param>
/// <returns>The parses <see cref="JsonWebToken"/></returns>
/// <exception cref="FormatException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
- public static JsonWebToken Parse(ReadOnlySpan<char> urlEncJwtString, IUnmangedHeap? heap = null)
+ public static JsonWebToken Parse(ReadOnlySpan<char> urlEncJwtString, IUnmangedHeap? heap = null, Encoding? textEncoding = null)
{
heap ??= MemoryUtil.Shared;
+ textEncoding ??= Encoding.UTF8;
+
+ if (urlEncJwtString.IsEmpty)
+ {
+ throw new ArgumentException("The JWT string is empty", nameof(urlEncJwtString));
+ }
//Calculate the decoded size of the characters to alloc a buffer
- int utf8Size = Encoding.UTF8.GetByteCount(urlEncJwtString);
+ int utf8Size = textEncoding.GetByteCount(urlEncJwtString);
//Alloc bin buffer to store decode data
using MemoryHandle<byte> binBuffer = heap.Alloc<byte>(utf8Size, true);
//Decode to utf8
- utf8Size = Encoding.UTF8.GetBytes(urlEncJwtString, binBuffer);
+ utf8Size = textEncoding.GetBytes(urlEncJwtString, binBuffer);
//Parse and return the jwt
return ParseRaw(binBuffer.Span[..utf8Size], heap);
@@ -93,18 +100,23 @@ namespace VNLib.Hashing.IdentityUtility
JsonWebToken jwt = new(heap, new (heap, utf8JWTData));
try
{
- ReadOnlySpan<byte> buffer = jwt.DataBuffer;
+ ForwardOnlyReader<byte> reader = new(utf8JWTData);
+
//Search for the first period to indicate the end of the header section
- jwt.HeaderEnd = buffer.IndexOf(SAEF_PERIOD);
+ jwt.HeaderEnd = reader.Window.IndexOf(SAEF_PERIOD);
+
//Make sure a '.' was found
if (jwt.HeaderEnd < 0)
{
throw new FormatException("The supplied data is not a valid Json Web Token, header end symbol could not be found");
}
+
//Shift buffer window
- buffer = buffer[jwt.PayloadStart..];
+ reader.Advance(jwt.PayloadStart);
+
//Search for next period to end the payload
- jwt.PayloadEnd = jwt.PayloadStart + buffer.LastIndexOf(SAEF_PERIOD);
+ jwt.PayloadEnd = jwt.PayloadStart + reader.Window.LastIndexOf(SAEF_PERIOD);
+
//Make sure a '.' was found
if (jwt.PayloadEnd < 0)
{
@@ -130,7 +142,7 @@ namespace VNLib.Hashing.IdentityUtility
/// The size (in bytes) of the encoded data that makes
/// up the current JWT.
/// </summary>
- public int ByteSize => (int)DataStream.Position;
+ public int ByteSize => Convert.ToInt32(DataStream.Position);
/// <summary>
/// A buffer that represents the current state of the JWT buffer.
/// Utf8Base64Url encoded data.
diff --git a/lib/Net.Http/src/Core/Response/ChunkedStream.cs b/lib/Net.Http/src/Core/Response/ChunkedStream.cs
index 953d763..724e28d 100644
--- a/lib/Net.Http/src/Core/Response/ChunkedStream.cs
+++ b/lib/Net.Http/src/Core/Response/ChunkedStream.cs
@@ -70,7 +70,7 @@ namespace VNLib.Net.Http.Core
LastChunk = encoding.GetBytes(LAST_CHUNK_STRING);
//get the min buffer by rounding to the nearest page
- int actualBufSize = (chunkBufferSize / 4096 + 1) * 4096;
+ int actualBufSize = (int)MemoryUtil.NearestPage(chunkBufferSize);
//Init accumulator
ChunckAccumulator = new(encoding, actualBufSize);
diff --git a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
index 36ebb66..fb20e68 100644
--- a/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
+++ b/lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs
@@ -27,8 +27,6 @@ using System.Runtime.InteropServices;
using VNLib.Utils.Memory;
-
-
namespace VNLib.Net.Http.Core
{
sealed class SharedHeaderReaderBuffer : IHttpLifeCycle
@@ -46,6 +44,9 @@ namespace VNLib.Net.Http.Core
{
_bufferSize = length + (length * sizeof(char));
+ //Round to nearest page
+ _bufferSize = (int)MemoryUtil.NearestPage(_bufferSize);
+
//Bin buffer is the specified size
BinLength = length;
}
diff --git a/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs b/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs
index 102b6c9..9b67143 100644
--- a/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs
+++ b/lib/Net.Messaging.FBM/src/Client/ClientExtensions.cs
@@ -25,6 +25,8 @@
using System;
using System.Runtime.CompilerServices;
+using VNLib.Utils;
+
namespace VNLib.Net.Messaging.FBM.Client
{
@@ -57,6 +59,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// </summary>
/// <param name="response"></param>
/// <exception cref="InvalidResponseException"></exception>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfNotSet(this in FBMResponse response)
{
@@ -64,6 +67,12 @@ namespace VNLib.Net.Messaging.FBM.Client
{
throw new InvalidResponseException("The response state is undefined (no response received)");
}
+
+ //Also throw if buffer header buffer size was too small
+ if(response.StatusFlags == HeaderParseError.HeaderOutOfMem)
+ {
+ throw new InternalBufferTooSmallException("The internal header buffer was too small to store response headers");
+ }
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs b/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs
new file mode 100644
index 0000000..95a5bb8
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs
@@ -0,0 +1,169 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: FBMBuffer.cs
+*
+* FBMBuffer.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 VNLib.Utils.IO;
+
+namespace VNLib.Net.Messaging.FBM.Client
+{
+ /// <summary>
+ /// Represents a shared internal character and bianry buffer for
+ /// </summary>
+ internal sealed class FBMBuffer : IDisposable
+ {
+ private readonly IMemoryOwner<byte> Handle;
+
+ private readonly BufferWriter _writer;
+ private readonly BinaryRequestAccumulator _requestAccumulator;
+
+
+ internal FBMBuffer(IMemoryOwner<byte> handle)
+ {
+ Handle = handle;
+ _writer = new(this);
+ _requestAccumulator = new(handle.Memory);
+ }
+
+
+ /// <summary>
+ /// Gets the internal request data accumulator
+ /// </summary>
+ public IDataAccumulator<byte> RequestBuffer => _requestAccumulator;
+
+ /// <summary>
+ /// Gets the accumulated request data for reading
+ /// </summary>
+ /// <returns>The accumulated request data as memory</returns>
+ public ReadOnlyMemory<byte> RequestData => _requestAccumulator.AccumulatedMemory;
+
+ /// <summary>
+ /// Completes the header segment and prepares the body writer
+ /// </summary>
+ /// <returns>A <see cref="IBufferWriter{T}"/> for writing an FBM message body to</returns>
+ public IBufferWriter<byte> GetBodyWriter()
+ {
+ //complete the header segments by writing an empty line
+ Helpers.WriteTermination(RequestBuffer);
+
+ //Return the internal writer
+ return _writer;
+ }
+
+ /// <summary>
+ /// Gets the buffer manager for managing response headers
+ /// </summary>
+ /// <returns>The <see cref="FBMHeaderBuffer"/> for managing response header buffers</returns>
+ public FBMHeaderBuffer GetResponseHeaderBuffer()
+ {
+ //Get a buffer wrapper around the memory handle
+ return new FBMHeaderBuffer(Handle.Memory);
+ }
+
+ public void Dispose()
+ {
+ //Dispose handle
+ Handle.Dispose();
+ }
+
+ /// <summary>
+ /// Resets the request accumulator and writes the initial message id
+ /// </summary>
+ /// <param name="messageId">The message id</param>
+ public void Reset(int messageId)
+ {
+ //Reset request header accumulator when complete
+ _requestAccumulator.Reset();
+
+ //Write message id to accumulator, it should already be reset
+ Helpers.WriteMessageid(RequestBuffer, messageId);
+ }
+
+ private sealed class BinaryRequestAccumulator : IDataAccumulator<byte>
+ {
+ private readonly int Size;
+ private readonly Memory<byte> Buffer;
+
+ internal BinaryRequestAccumulator(Memory<byte> buffer)
+ {
+ Buffer = buffer;
+ Size = buffer.Length;
+ }
+
+ ///<inheritdoc/>
+ public int AccumulatedSize { get; private set; }
+
+ ///<inheritdoc/>
+ public int RemainingSize => Size - AccumulatedSize;
+
+ ///<inheritdoc/>
+ public Span<byte> Remaining => Buffer.Span.Slice(AccumulatedSize, RemainingSize);
+ ///<inheritdoc/>
+ public Span<byte> Accumulated => Buffer.Span[..AccumulatedSize];
+
+ /// <summary>
+ /// Gets the accumulated data as a memory segment
+ /// </summary>
+ public Memory<byte> AccumulatedMemory => Buffer[..AccumulatedSize];
+
+ /// <summary>
+ /// Gets the remaining buffer segment as a memory segment
+ /// </summary>
+ public Memory<byte> RemainingMemory => Buffer.Slice(AccumulatedSize, RemainingSize);
+
+ ///<inheritdoc/>
+ public void Advance(int count) => AccumulatedSize += count;
+ ///<inheritdoc/>
+ public void Reset() => AccumulatedSize = 0;
+ }
+
+ private sealed class BufferWriter : IBufferWriter<byte>
+ {
+ private readonly FBMBuffer Buffer;
+
+ public BufferWriter(FBMBuffer buffer)
+ {
+ Buffer = buffer;
+ }
+
+ public void Advance(int count)
+ {
+ //Advance the writer
+ Buffer.RequestBuffer.Advance(count);
+ }
+
+ public Memory<byte> GetMemory(int sizeHint = 0)
+ {
+ //Get the remaining memory segment
+ return Buffer._requestAccumulator.RemainingMemory;
+ }
+
+ public Span<byte> GetSpan(int sizeHint = 0)
+ {
+ //Get the remaining data segment
+ return Buffer.RequestBuffer.Remaining;
+ }
+ }
+ }
+}
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs
index db52c03..c4f9f54 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMClient.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMClient.cs
@@ -199,7 +199,10 @@ namespace VNLib.Net.Messaging.FBM.Client
}
try
{
- Debug("Sending {bytes} with id {id}", request.RequestData.Length, request.MessageId);
+ //Get the request data segment
+ ReadOnlyMemory<byte> requestData = request.GetRequestData();
+
+ Debug("Sending {bytes} with id {id}", requestData.Length, request.MessageId);
//Reset the wait handle
request.ResponseWaitEvent.Reset();
@@ -208,7 +211,7 @@ namespace VNLib.Net.Messaging.FBM.Client
using (SemSlimReleaser releaser = await SendLock.GetReleaserAsync(cancellationToken))
{
//Send the data to the server
- await ClientSocket.SendAsync(request.RequestData, WebSocketMessageType.Binary, true, cancellationToken);
+ await ClientSocket.SendAsync(requestData, WebSocketMessageType.Binary, true, cancellationToken);
}
//wait for the response to be set
@@ -253,16 +256,22 @@ namespace VNLib.Net.Messaging.FBM.Client
}
try
{
- Debug("Streaming {bytes} with id {id}", request.RequestData.Length, request.MessageId);
+ //Get the request data segment
+ ReadOnlyMemory<byte> requestData = request.GetRequestData();
+
+ Debug("Streaming {bytes} with id {id}", requestData.Length, request.MessageId);
+
//Reset the wait handle
request.ResponseWaitEvent.Reset();
+
//Write an empty body in the request
request.WriteBody(ReadOnlySpan<byte>.Empty, ct);
+
//Wait for send-lock
using (SemSlimReleaser releaser = await SendLock.GetReleaserAsync(cancellationToken))
{
//Send the initial request packet
- await ClientSocket.SendAsync(request.RequestData, WebSocketMessageType.Binary, false, cancellationToken);
+ await ClientSocket.SendAsync(requestData, WebSocketMessageType.Binary, false, cancellationToken);
//Calc buffer size
int bufSize = (int)Math.Clamp(payload.Length, Config.MessageBufferSize, Config.MaxMessageSize);
//Alloc a streaming buffer
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs b/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs
index 229eb76..c6082f0 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMClientConfig.cs
@@ -36,7 +36,8 @@ namespace VNLib.Net.Messaging.FBM.Client
public readonly struct FBMClientConfig
{
/// <summary>
- /// The size (in bytes) of the internal buffer to use when receiving messages from the server
+ /// The size (in bytes) of the internal buffer used to buffer incomming messages,
+ /// this value will also be sent to the server to synchronous recv buffer sizes
/// </summary>
public readonly int RecvBufferSize { get; init; }
/// <summary>
@@ -48,7 +49,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// </remarks>
public readonly int MessageBufferSize { get; init; }
/// <summary>
- /// The size (in chars) of the client/server message header buffer
+ /// The size (in bytes) of the client/server message header buffer
/// </summary>
public readonly int MaxHeaderBufferSize { get; init; }
/// <summary>
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs
index 0e46582..aaf3926 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs
@@ -28,6 +28,7 @@ using System.Buffers;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
using VNLib.Net.Http;
using VNLib.Utils;
@@ -38,61 +39,34 @@ using VNLib.Utils.Memory.Caching;
namespace VNLib.Net.Messaging.FBM.Client
{
+
/// <summary>
+ /// <para>
/// A reusable Fixed Buffer Message request container. This class is not thread-safe
+ /// </para>
+ /// <para>
+ /// The internal buffer is used for storing headers, body data (unless streaming)
+ /// </para>
/// </summary>
public sealed class FBMRequest : VnDisposeable, IReusable, IFBMMessage, IStringSerializeable
{
- private sealed class BufferWriter : IBufferWriter<byte>
- {
- private readonly FBMRequest _request;
-
- public BufferWriter(FBMRequest request)
- {
- _request = request;
- }
-
- public void Advance(int count)
- {
- _request.Position += count;
- }
-
- public Memory<byte> GetMemory(int sizeHint = 0)
- {
- return sizeHint > 0 ? _request.RemainingBuffer[0..sizeHint] : _request.RemainingBuffer;
- }
-
- public Span<byte> GetSpan(int sizeHint = 0)
- {
- return sizeHint > 0 ? _request.RemainingBuffer.Span[0..sizeHint] : _request.RemainingBuffer.Span;
- }
- }
-
- private readonly IMemoryOwner<byte> HeapBuffer;
-
-
- private readonly BufferWriter _writer;
- private int Position;
-
+#pragma warning disable CA2213 // Disposable fields should be disposed
+ private readonly FBMBuffer Buffer;
+#pragma warning restore CA2213 // Disposable fields should be disposed
private readonly Encoding HeaderEncoding;
- private readonly int ResponseHeaderBufferSize;
- private readonly List<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> ResponseHeaderList = new();
- private char[]? ResponseHeaderBuffer;
+
+ private readonly List<FBMMessageHeader> ResponseHeaderList = new();
/// <summary>
/// The size (in bytes) of the request message
/// </summary>
- public int Length => Position;
- private Memory<byte> RemainingBuffer => HeapBuffer.Memory[Position..];
+ public int Length => Buffer.RequestBuffer.AccumulatedSize;
/// <summary>
/// The id of the current request message
/// </summary>
public int MessageId { get; }
- /// <summary>
- /// The request message packet
- /// </summary>
- public ReadOnlyMemory<byte> RequestData => HeapBuffer.Memory[..Position];
+
/// <summary>
/// An <see cref="ManualResetEvent"/> to signal request/response
/// event completion
@@ -100,6 +74,7 @@ namespace VNLib.Net.Messaging.FBM.Client
internal ManualResetEvent ResponseWaitEvent { get; }
internal VnMemoryStream? Response { get; private set; }
+
/// <summary>
/// Initializes a new <see cref="FBMRequest"/> with the sepcified message buffer size,
/// and a random messageid
@@ -107,77 +82,71 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <param name="config">The fbm client config storing required config variables</param>
public FBMRequest(in FBMClientConfig config) : this(Helpers.RandomMessageId, in config)
{ }
+
/// <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="config">The fbm client config storing required config variables</param>
public FBMRequest(int messageId, in FBMClientConfig config)
- {
- //Setup response wait handle but make sure the contuation runs async
- ResponseWaitEvent = new(true);
-
- //Alloc the buffer as a memory owner so a memory buffer can be used
- HeapBuffer = config.BufferHeap.DirectAlloc<byte>(config.MessageBufferSize);
-
- MessageId = messageId;
-
- HeaderEncoding = config.HeaderEncoding;
- ResponseHeaderBufferSize = config.MaxHeaderBufferSize;
+ :this(messageId, config.BufferHeap, config.MessageBufferSize, config.HeaderEncoding)
+ { }
- WriteMessageId();
- _writer = new(this);
- }
/// <summary>
- /// Resets the internal buffer and writes the message-id header to the begining
- /// of the buffer
+ /// Initializes a new <see cref="FBMRequest"/> with the sepcified message buffer size and a custom MessageId
/// </summary>
- private void WriteMessageId()
+ /// <param name="messageId">The custom message id</param>
+ /// <param name="heap">The heap to allocate the internal buffer from</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)
{
- //Get writer over buffer
- ForwardOnlyWriter<byte> buffer = new(HeapBuffer.Memory.Span);
- //write messageid header to the buffer
- buffer.Append((byte)HeaderCommand.MessageId);
- buffer.Append(MessageId);
- buffer.WriteTermination();
- //Store intial position
- Position = buffer.Written;
+ MessageId = messageId;
+ HeaderEncoding = headerEncoding;
+
+ //Alloc the buffer as a memory owner so a memory buffer can be used
+ IMemoryOwner<byte> HeapBuffer = heap.DirectAlloc<byte>(bufferSize);
+ Buffer = new(HeapBuffer);
+
+ //Setup response wait handle but make sure the contuation runs async
+ ResponseWaitEvent = new(true);
+
+ //Prepare the message incase the request is fresh
+ Reset();
}
///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value) => WriteHeader((byte)header, value);
///<inheritdoc/>
- public void WriteHeader(byte header, ReadOnlySpan<char> value)
- {
- ForwardOnlyWriter<byte> buffer = new(RemainingBuffer.Span);
- buffer.WriteHeader(header, value, Helpers.DefaultEncoding);
- //Update position
- Position += buffer.Written;
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void WriteHeader(byte header, ReadOnlySpan<char> value) => Helpers.WriteHeader(Buffer.RequestBuffer, header, value, Helpers.DefaultEncoding);
///<inheritdoc/>
public void WriteBody(ReadOnlySpan<byte> body, ContentType contentType = ContentType.Binary)
{
//Write content type header
WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(contentType));
- //Get writer over buffer
- ForwardOnlyWriter<byte> buffer = new(RemainingBuffer.Span);
//Now safe to write body
- buffer.WriteBody(body);
- //Update position
- Position += buffer.Written;
+ Helpers.WriteBody(Buffer.RequestBuffer, body);
}
+
/// <summary>
/// Returns buffer writer for writing the body data to the internal message buffer
/// </summary>
- /// <returns>A <see cref="BufferWriter"/> to write message body to</returns>
- public IBufferWriter<byte> GetBodyWriter()
+ /// <returns>A <see cref="IBufferWriter{T}"/> to write message body to</returns>
+ /// <remarks>Calling this method ends the headers section of the request</remarks>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public IBufferWriter<byte> GetBodyWriter() => Buffer.GetBodyWriter();
+
+
+ /// <summary>
+ /// The request message packet, this may cause side effects
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyMemory<byte> GetRequestData()
{
- //Write body termination
- Helpers.Termination.CopyTo(RemainingBuffer);
- Position += Helpers.Termination.Length;
- //Return buffer writer
- return _writer;
+ return Buffer.RequestData;
}
/// <summary>
@@ -186,8 +155,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// </summary>
public void Reset()
{
- //Re-writing the message-id will reset the buffer
- WriteMessageId();
+ Buffer.Reset(MessageId);
}
internal void SetResponse(VnMemoryStream? vms)
@@ -204,20 +172,25 @@ namespace VNLib.Net.Messaging.FBM.Client
///<inheritdoc/>
protected override void Free()
{
- HeapBuffer.Dispose();
+ Buffer.Dispose();
ResponseWaitEvent.Dispose();
- OnResponseDisposed();
+ Response?.Dispose();
}
void IReusable.Prepare() => Reset();
bool IReusable.Release()
{
+ //Make sure response header list is clear
+ ResponseHeaderList.Clear();
+
//Clear old response data if error occured
Response?.Dispose();
Response = null;
-
+
return true;
}
+ #region Response
+
/// <summary>
/// Gets the response of the sent message
/// </summary>
@@ -237,13 +210,12 @@ namespace VNLib.Net.Messaging.FBM.Client
* The headers are read into a list of key-value pairs and the stream
* is positioned to the start of the message body
*/
-
-
- //Alloc rseponse buffer
- ResponseHeaderBuffer ??= ArrayPool<char>.Shared.Rent(ResponseHeaderBufferSize);
+
+ //Get the response header buffer for parsing
+ FBMHeaderBuffer headerBuf = Buffer.GetResponseHeaderBuffer();
//Parse message headers
- HeaderParseError statusFlags = Helpers.ParseHeaders(Response, ResponseHeaderBuffer, ResponseHeaderList, HeaderEncoding);
+ HeaderParseError statusFlags = Helpers.ParseHeaders(Response, in headerBuf, ResponseHeaderList, HeaderEncoding);
//return response structure
return new(Response, statusFlags, ResponseHeaderList, OnResponseDisposed);
@@ -263,19 +235,17 @@ namespace VNLib.Net.Messaging.FBM.Client
//Clear old response
Response?.Dispose();
Response = null;
-
- if (ResponseHeaderBuffer != null)
- {
- //Free response buffer
- ArrayPool<char>.Shared.Return(ResponseHeaderBuffer!);
- ResponseHeaderBuffer = null;
- }
}
+ #endregion
+
+ #region Diagnostics
///<inheritdoc/>
public string Compile()
{
- int charSize = Helpers.DefaultEncoding.GetCharCount(RequestData.Span);
+ ReadOnlyMemory<byte> requestData = GetRequestData();
+
+ int charSize = Helpers.DefaultEncoding.GetCharCount(requestData.Span);
using UnsafeMemoryHandle<char> buffer = MemoryUtil.UnsafeAlloc<char>(charSize + 128);
@@ -286,10 +256,11 @@ namespace VNLib.Net.Messaging.FBM.Client
///<inheritdoc/>
public void Compile(ref ForwardOnlyWriter<char> writer)
{
+ ReadOnlyMemory<byte> requestData = GetRequestData();
writer.Append("Message ID:");
writer.Append(MessageId);
writer.Append(Environment.NewLine);
- Helpers.DefaultEncoding.GetChars(RequestData.Span, ref writer);
+ Helpers.DefaultEncoding.GetChars(requestData.Span, ref writer);
}
///<inheritdoc/>
public ERRNO Compile(in Span<char> buffer)
@@ -300,6 +271,7 @@ namespace VNLib.Net.Messaging.FBM.Client
}
///<inheritdoc/>
public override string ToString() => Compile();
-
+
+ #endregion
}
}
diff --git a/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs b/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs
index da36956..6f8fec4 100644
--- a/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs
+++ b/lib/Net.Messaging.FBM/src/Client/FBMResponse.cs
@@ -48,7 +48,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <summary>
/// A collection of response message headers
/// </summary>
- public readonly IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> Headers { get; }
+ public readonly IReadOnlyList<FBMMessageHeader> Headers { get; }
/// <summary>
/// Status flags of the message parse operation
/// </summary>
@@ -66,7 +66,7 @@ namespace VNLib.Net.Messaging.FBM.Client
/// <param name="status">The size of the buffer to alloc for header value storage</param>
/// <param name="headerList">The collection of headerse</param>
/// <param name="onDispose">A method that will be invoked when the message response body is disposed</param>
- public FBMResponse(VnMemoryStream? vms, HeaderParseError status, IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> headerList, Action onDispose)
+ public FBMResponse(VnMemoryStream? vms, HeaderParseError status, IReadOnlyList<FBMMessageHeader> headerList, Action onDispose)
{
MessagePacket = vms;
StatusFlags = status;
@@ -82,7 +82,7 @@ namespace VNLib.Net.Messaging.FBM.Client
{
MessagePacket = null;
StatusFlags = HeaderParseError.InvalidHeaderRead;
- Headers = Array.Empty<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>>();
+ Headers = Array.Empty<FBMMessageHeader>();
IsSet = false;
_onDispose = null;
}
diff --git a/lib/Net.Messaging.FBM/src/Client/Helpers.cs b/lib/Net.Messaging.FBM/src/Client/Helpers.cs
index 054a30b..0b9e7bd 100644
--- a/lib/Net.Messaging.FBM/src/Client/Helpers.cs
+++ b/lib/Net.Messaging.FBM/src/Client/Helpers.cs
@@ -25,15 +25,16 @@
using System;
using System.IO;
using System.Text;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.Security.Cryptography;
+using System.Runtime.CompilerServices;
using VNLib.Utils;
using VNLib.Utils.IO;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
-
namespace VNLib.Net.Messaging.FBM
{
/// <summary>
@@ -46,8 +47,15 @@ namespace VNLib.Net.Messaging.FBM
/// </summary>
public const int CONTROL_FRAME_MID = -500;
- public static readonly Encoding DefaultEncoding = Encoding.UTF8;
- public static readonly ReadOnlyMemory<byte> Termination = new byte[] { 0xFF, 0xF1 };
+ /// <summary>
+ /// Gets the default header character encoding instance
+ /// </summary>
+ public static Encoding DefaultEncoding { get; } = Encoding.UTF8;
+
+ /// <summary>
+ /// The FBM protocol header line termination symbols
+ /// </summary>
+ public static ReadOnlyMemory<byte> Termination { get; } = new byte[] { 0xFF, 0xF1 };
/// <summary>
/// Parses the header line for a message-id
@@ -61,29 +69,57 @@ namespace VNLib.Net.Messaging.FBM
{
return -1;
}
+
//The first byte should be the header id
HeaderCommand headerId = (HeaderCommand)line[0];
+
//Make sure the headerid is set
if (headerId != HeaderCommand.MessageId)
{
return -2;
}
+
//Get the messageid after the header byte
ReadOnlySpan<byte> messageIdSegment = line.Slice(1, sizeof(int));
+
//get the messageid from the messageid segment
- return BitConverter.ToInt32(messageIdSegment);
+ return BinaryPrimitives.ReadInt32BigEndian(messageIdSegment);
}
/// <summary>
+ /// Appends the message id header to the accumulator
+ /// </summary>
+ /// <param name="accumulator">The accumulatore to write the message id to</param>
+ /// <param name="messageid">The message id to write to the accumulator</param>
+ public static void WriteMessageid(IDataAccumulator<byte> accumulator, int messageid)
+ {
+ //Alloc buffer for message id + the message id header
+ Span<byte> buffer = stackalloc byte[sizeof(int) + 1];
+
+ //Set 1st byte as message id
+ buffer[0] = (byte)HeaderCommand.MessageId;
+
+ //Write the message id as a big-endian message
+ BinaryPrimitives.WriteInt32BigEndian(buffer[1..], messageid);
+
+ //Write the header and message id + the trailing termination
+ accumulator.Append(buffer);
+
+ WriteTermination(accumulator);
+ }
+
+
+ /// <summary>
/// Alloctes a random integer to use as a message id
/// </summary>
public static int RandomMessageId => RandomNumberGenerator.GetInt32(1, int.MaxValue);
-
+
/// <summary>
/// Gets the remaining data after the current position of the stream.
/// </summary>
/// <param name="response">The stream to segment</param>
/// <returns>The remaining data segment</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<byte> GetRemainingData(VnMemoryStream response)
{
return response.AsSpan()[(int)response.Position..];
@@ -109,6 +145,7 @@ namespace VNLib.Net.Messaging.FBM
//slice up line and exclude the termination
return line[..index];
}
+
/// <summary>
/// Parses headers from the request stream, stores headers from the buffer into the
/// header collection
@@ -118,24 +155,31 @@ namespace VNLib.Net.Messaging.FBM
/// <param name="headers">The collection to store headers in</param>
/// <param name="encoding">The encoding type used to deocde header values</param>
/// <returns>The results of the parse operation</returns>
- public static HeaderParseError ParseHeaders(VnMemoryStream vms, char[] buffer, ICollection<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> headers, Encoding encoding)
+ internal static HeaderParseError ParseHeaders(VnMemoryStream vms, in FBMHeaderBuffer buffer, ICollection<FBMMessageHeader> headers, Encoding encoding)
{
HeaderParseError status = HeaderParseError.None;
- //sliding window
- Memory<char> currentWindow = buffer;
+
+ //Get a sliding window writer over the enitre buffer
+ ForwardOnlyWriter<char> writer = new(buffer.GetSpan());
+
//Accumulate headers
while (true)
{
//Read the next line from the current stream
ReadOnlySpan<byte> line = ReadLine(vms);
+
if (line.IsEmpty)
{
//Done reading headers
break;
}
+
+ //Read the header command from the next line
HeaderCommand cmd = GetHeaderCommand(line);
- //Get header value
- ERRNO charsRead = GetHeaderValue(line, currentWindow.Span, encoding);
+
+ //Write the next header value from the line to the remaining space in the buffer
+ ERRNO charsRead = GetHeaderValue(line, writer.Remaining, encoding);
+
if (charsRead < 0)
{
//Out of buffer space
@@ -149,10 +193,14 @@ namespace VNLib.Net.Messaging.FBM
}
else
{
+ //Use the writer to capture the offset, and the character size
+ FBMMessageHeader header = new(buffer, cmd, writer.Written, (int)charsRead);
+
//Store header as a read-only sequence
- headers.Add(new(cmd, currentWindow[..(int)charsRead]));
- //Shift buffer window
- currentWindow = currentWindow[(int)charsRead..];
+ headers.Add(header);
+
+ //Advance the writer
+ writer.Advance(charsRead);
}
}
return status;
@@ -163,10 +211,12 @@ namespace VNLib.Net.Messaging.FBM
/// </summary>
/// <param name="line"></param>
/// <returns>The <see cref="HeaderCommand"/> enum value from hte first byte of the message</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static HeaderCommand GetHeaderCommand(ReadOnlySpan<byte> line)
{
return (HeaderCommand)line[0];
}
+
/// <summary>
/// Gets the value of the header following the colon bytes in the specifed
/// data message data line
@@ -174,7 +224,7 @@ namespace VNLib.Net.Messaging.FBM
/// <param name="line">The message header line to get the value of</param>
/// <param name="output">The output character buffer to write characters to</param>
/// <param name="encoding">The encoding to decode the specified data with</param>
- /// <returns>The number of characters encoded</returns>
+ /// <returns>The number of characters encoded or -1 if the output buffer is too small</returns>
public static ERRNO GetHeaderValue(ReadOnlySpan<byte> line, Span<char> output, Encoding encoding)
{
//Get the data following the header byte
@@ -190,31 +240,7 @@ namespace VNLib.Net.Messaging.FBM
_ = encoding.GetChars(value, output);
return charCount;
}
-
- /// <summary>
- /// Appends an arbitrary header to the current request buffer
- /// </summary>
- /// <param name="buffer"></param>
- /// <param name="header">The <see cref="HeaderCommand"/> of the header</param>
- /// <param name="value">The value of the header</param>
- /// <param name="encoding">Encoding to use when writing character message</param>
- /// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void WriteHeader(ref this ForwardOnlyWriter<byte> buffer, byte header, ReadOnlySpan<char> value, Encoding encoding)
- {
- //get char count
- int byteCount = encoding.GetByteCount(value);
- //make sure there is enough room in the buffer
- if (buffer.RemainingSize < byteCount)
- {
- throw new ArgumentOutOfRangeException(nameof(value),"The internal buffer is too small to write header");
- }
- //Write header command enum value
- buffer.Append(header);
- //Convert the characters to binary and write to the buffer
- encoding.GetBytes(value, ref buffer);
- //Write termination (0)
- buffer.WriteTermination();
- }
+
/// <summary>
/// Ends the header section of the request and appends the message body to
@@ -223,32 +249,38 @@ namespace VNLib.Net.Messaging.FBM
/// <param name="buffer"></param>
/// <param name="body">The message body to send with request</param>
/// <exception cref="OutOfMemoryException"></exception>
- public static void WriteBody(ref this ForwardOnlyWriter<byte> buffer, ReadOnlySpan<byte> body)
+ public static void WriteBody(IDataAccumulator<byte> buffer, ReadOnlySpan<byte> body)
{
//start with termination
- buffer.WriteTermination();
+ WriteTermination(buffer);
//Write the body
buffer.Append(body);
}
+
+
/// <summary>
- /// Writes a line termination to the message buffer
+ /// Rounds the requested byte size up to the 1kb
+ /// number of bytes
/// </summary>
- /// <param name="buffer"></param>
- public static void WriteTermination(ref this ForwardOnlyWriter<byte> buffer)
+ /// <param name="byteSize">The number of bytes to get the rounded 1kb size of</param>
+ /// <returns>The number of bytes equivalent to the requested byte size rounded to the next 1kb size</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint ToNearestKb(nint byteSize)
{
- //write termination
- buffer.Append(Termination.Span);
+ //Get page count by dividing count by number of pages
+ nint kbs = (nint)Math.Ceiling(byteSize / (double)1024);
+
+ //Multiply back to page sizes
+ return kbs * 1024;
}
+
/// <summary>
/// Writes a line termination to the message buffer
/// </summary>
/// <param name="buffer"></param>
- public static void WriteTermination(this IDataAccumulator<byte> buffer)
- {
- //write termination
- buffer.Append(Termination.Span);
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteTermination(IDataAccumulator<byte> buffer) => buffer.Append(Termination.Span);
/// <summary>
/// Appends an arbitrary header to the current request buffer
@@ -258,7 +290,7 @@ namespace VNLib.Net.Messaging.FBM
/// <param name="value">The value of the header</param>
/// <param name="encoding">Encoding to use when writing character message</param>
/// <exception cref="ArgumentException"></exception>
- public static void WriteHeader(this IDataAccumulator<byte> buffer, byte header, ReadOnlySpan<char> value, Encoding encoding)
+ public static void WriteHeader(IDataAccumulator<byte> buffer, byte header, ReadOnlySpan<char> value, Encoding encoding)
{
//Write header command enum value
buffer.Append(header);
@@ -267,7 +299,7 @@ namespace VNLib.Net.Messaging.FBM
//Advance the buffer
buffer.Advance(written);
//Write termination (0)
- buffer.WriteTermination();
+ WriteTermination(buffer);
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Client/README.md b/lib/Net.Messaging.FBM/src/Client/README.md
index 5aa8e76..35f49ff 100644
--- a/lib/Net.Messaging.FBM/src/Client/README.md
+++ b/lib/Net.Messaging.FBM/src/Client/README.md
@@ -18,7 +18,7 @@ messages. Headers are identified by a single byte, followed by a variable length
encoded character sequence, followed by a termination of 0xFF, 0xF1 (may change).
### Message structure
- 4 byte positive (signed 32-bit integer) message id
+ 4 byte positive (big endian signed 32-bit integer) message id
2 byte termination
1 byte header-id
variable length UTF8 value
@@ -140,6 +140,9 @@ alter the state of the connection (negotiation etc). A mechanism to do so is pro
{
//Extension method to raise exception if an invalid response was received (also use the response.IsSet flag)
response.ThrowIfNotSet();
+
+ //Check header parse status
+ //response.StatusFlags
//Check headers (using Linq to get first header)
string header1 = response.Headers.First().Value.ToString();
diff --git a/lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs b/lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs
new file mode 100644
index 0000000..42ec2af
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs
@@ -0,0 +1,60 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: FBMHeaderBuffer.cs
+*
+* FBMHeaderBuffer.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.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
+
+namespace VNLib.Net.Messaging.FBM
+{
+ internal readonly struct FBMHeaderBuffer
+ {
+ private readonly Memory<byte> _handle;
+
+ internal FBMHeaderBuffer(Memory<byte> handle) => _handle = handle;
+
+ /// <summary>
+ /// Gets a character squence within the binary buffer of the specified
+ /// character offset and length
+ /// </summary>
+ /// <param name="offset">The character offset within the internal buffer</param>
+ /// <param name="count">The number of characters within the desired span</param>
+ /// <returns>A span at the given character offset and of the specified length</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span<char> GetSpan(int offset, int count)
+ {
+ //Get the character span
+ Span<char> span = GetSpan();
+ return span.Slice(offset, count);
+ }
+
+ /// <summary>
+ /// Gets the entire internal buffer as a character span
+ /// </summary>
+ /// <returns>A span over the entire internal buffer</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span<char> GetSpan() => MemoryMarshal.Cast<byte, char>(_handle.Span);
+ }
+}
diff --git a/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs
new file mode 100644
index 0000000..e0a33ef
--- /dev/null
+++ b/lib/Net.Messaging.FBM/src/FBMMessageHeader.cs
@@ -0,0 +1,94 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Messaging.FBM
+* File: FBMMessageHeader.cs
+*
+* FBMMessageHeader.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 Key-Value pair FBM response message header
+ /// </summary>
+ public readonly struct FBMMessageHeader : IEquatable<FBMMessageHeader>
+ {
+ private readonly FBMHeaderBuffer _buffer;
+ private readonly int _headerOffset;
+ private readonly int _headerLength;
+
+ /// <summary>
+ /// The header value or command
+ /// </summary>
+ public readonly HeaderCommand Header { get; }
+
+ /// <summary>
+ /// The header value
+ /// </summary>
+ public readonly ReadOnlySpan<char> Value => _buffer.GetSpan(_headerOffset, _headerLength);
+
+ /// <summary>
+ /// Gets the header value as a <see cref="string"/>
+ /// </summary>
+ /// <returns>The allocates string of the value</returns>
+ public readonly string GetValueString() => Value.ToString();
+
+ /// <summary>
+ /// Initializes a new <see cref="FBMMessageHeader"/> of the sepcified command
+ /// that utilizes memory from the specified buffer
+ /// </summary>
+ /// <param name="buffer">The buffer that owns the memory our header is stored in</param>
+ /// <param name="command">The command this header represents</param>
+ /// <param name="offset">The char offset within the buffer our header begins</param>
+ /// <param name="length">The character length of our header value</param>
+ internal FBMMessageHeader(FBMHeaderBuffer buffer, HeaderCommand command, int offset, int length)
+ {
+ Header = command;
+ _buffer = buffer;
+ _headerLength = length;
+ _headerOffset = offset;
+ }
+
+ ///<inheritdoc/>
+ public override bool Equals(object? obj) => obj is FBMMessageHeader other && Equals(other);
+
+ /// <summary>
+ /// Calculates a hash code from the <see cref="Value"/> parameter
+ /// using original string hashcode computation
+ /// </summary>
+ /// <returns>The unique hashcode for the <see cref="Value"/> character sequence</returns>
+ public override int GetHashCode() => string.GetHashCode(Value, StringComparison.Ordinal);
+
+ ///<inheritdoc/>
+ public static bool operator ==(FBMMessageHeader left, FBMMessageHeader right) => left.Equals(right);
+ ///<inheritdoc/>
+ public static bool operator !=(FBMMessageHeader left, FBMMessageHeader right) => !(left == right);
+
+ /// <summary>
+ /// Determines if the other response header is equal to the current header by
+ /// comparing its command and its value
+ /// </summary>
+ /// <param name="other">The other header to compare</param>
+ /// <returns>True if both headers have the same commad and value sequence</returns>
+ public bool Equals(FBMMessageHeader other) => Header == other.Header && Value.SequenceEqual(other.Value);
+ }
+}
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMContext.cs b/lib/Net.Messaging.FBM/src/Server/FBMContext.cs
index fb39d1b..6d5f3bd 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMContext.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMContext.cs
@@ -35,6 +35,9 @@ namespace VNLib.Net.Messaging.FBM.Server
public sealed class FBMContext : IReusable
{
private readonly Encoding _headerEncoding;
+
+ private readonly IReusable _request;
+ private readonly IReusable _response;
/// <summary>
/// The request message to process
@@ -54,8 +57,8 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <param name="headerEncoding">The message header encoding instance</param>
public FBMContext(int requestHeaderBufferSize, int responseBufferSize, Encoding headerEncoding)
{
- Request = new(requestHeaderBufferSize);
- Response = new(responseBufferSize, headerEncoding);
+ _request = Request = new(requestHeaderBufferSize);
+ _response = Response = new(responseBufferSize, headerEncoding);
_headerEncoding = headerEncoding;
}
@@ -73,13 +76,13 @@ namespace VNLib.Net.Messaging.FBM.Server
void IReusable.Prepare()
{
- (Request as IReusable).Prepare();
- (Response as IReusable).Prepare();
+ _request.Prepare();
+ _response.Prepare();
}
bool IReusable.Release()
{
- return (Request as IReusable).Release() & (Response as IReusable).Release();
+ return _request.Release() & _response.Release();
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs
index 1388774..3417abc 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMListener.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMListener.cs
@@ -85,7 +85,6 @@ namespace VNLib.Net.Messaging.FBM.Server
Cancellation = new();
Registration = session.Token.Register(Cancellation.Cancel);
-
ResponseLock = new(1);
CtxStore = ObjectRental.CreateReusable(ContextCtor);
}
@@ -324,7 +323,11 @@ namespace VNLib.Net.Messaging.FBM.Server
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);
+ if (!session.ResponseLock.Wait(0))
+ {
+ await session.ResponseLock.WaitAsync(SEND_SEMAPHORE_TIMEOUT_MS);
+ }
+
try
{
do
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs b/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs
index c327475..ccf79db 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMListenerSessionParams.cs
@@ -38,7 +38,7 @@ namespace VNLib.Net.Messaging.FBM.Server
/// </summary>
public readonly int RecvBufferSize { get; init; }
/// <summary>
- /// The size of the character buffer to store FBMheader values in
+ /// The size of the buffer to store <see cref="FBMMessageHeader"/> values in
/// the <see cref="FBMRequestMessage"/>
/// </summary>
public readonly int MaxHeaderBufferSize { get; init; }
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs b/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
index ed36571..d37ba84 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMRequestMessage.cs
@@ -28,9 +28,7 @@ using System.Buffers;
using System.Text.Json;
using System.Collections.Generic;
-using VNLib.Utils;
using VNLib.Utils.IO;
-using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Utils.Memory.Caching;
@@ -41,19 +39,20 @@ namespace VNLib.Net.Messaging.FBM.Server
/// </summary>
public sealed class FBMRequestMessage : IReusable
{
- private readonly List<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> _headers;
- private readonly int HeaderCharBufferSize;
+ private readonly List<FBMMessageHeader> _headers;
+ private readonly int HeaderBufferSize;
+
/// <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)
{
- HeaderCharBufferSize = headerBufferSize;
+ HeaderBufferSize = headerBufferSize;
_headers = new();
}
- private char[]? _headerBuffer;
+ private byte[]? _headerBuffer;
/// <summary>
/// The ID of the current message
@@ -70,7 +69,7 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <summary>
/// A collection of headers for the current request
/// </summary>
- public IReadOnlyList<KeyValuePair<HeaderCommand, ReadOnlyMemory<char>>> Headers => _headers;
+ public IReadOnlyList<FBMMessageHeader> Headers => _headers;
/// <summary>
/// Status flags set during the message parsing
/// </summary>
@@ -95,8 +94,10 @@ namespace VNLib.Net.Messaging.FBM.Server
{
//Store request body
RequestBody = vms;
+
//Store message id
MessageId = Helpers.GetMessageId(Helpers.ReadLine(vms));
+
//Check mid for control frame
if(MessageId == Helpers.CONTROL_FRAME_MID)
{
@@ -110,41 +111,11 @@ namespace VNLib.Net.Messaging.FBM.Server
ConnectionId = socketId;
- //sliding window over remaining data from internal buffer
- ForwardOnlyMemoryWriter<char> writer = new(_headerBuffer);
-
- //Accumulate headers
- while (true)
- {
- //Read the next line from the current stream
- ReadOnlySpan<byte> line = Helpers.ReadLine(vms);
- if (line.IsEmpty)
- {
- //Done reading headers
- break;
- }
- HeaderCommand cmd = Helpers.GetHeaderCommand(line);
- //Get header value
- ERRNO charsRead = Helpers.GetHeaderValue(line, writer.Remaining.Span, dataEncoding);
- if (charsRead < 0)
- {
- //Out of buffer space
- ParseStatus |= HeaderParseError.HeaderOutOfMem;
- break;
- }
- else if (!charsRead)
- {
- //Invalid header
- ParseStatus |= HeaderParseError.InvalidHeaderRead;
- }
- else
- {
- //Store header as a read-only sequence
- _headers.Add(new(cmd, writer.Remaining[..(int)charsRead]));
- //Shift buffer window
- writer.Advance(charsRead);
- }
- }
+ //Get mesage buffer wrapper around the header
+ FBMHeaderBuffer buffer = new(_headerBuffer);
+
+ //Parse headers
+ ParseStatus = Helpers.ParseHeaders(vms, in buffer, _headers, dataEncoding);
}
/// <summary>
@@ -158,6 +129,7 @@ namespace VNLib.Net.Messaging.FBM.Server
{
return BodyData.IsEmpty ? default : BodyData.AsJsonObject<T>(jso);
}
+
/// <summary>
/// Gets a <see cref="JsonDocument"/> of the request body
/// </summary>
@@ -173,7 +145,7 @@ namespace VNLib.Net.Messaging.FBM.Server
{
ParseStatus = HeaderParseError.None;
//Alloc header buffer
- _headerBuffer = ArrayPool<char>.Shared.Rent(HeaderCharBufferSize);
+ _headerBuffer = ArrayPool<byte>.Shared.Rent(HeaderBufferSize);
}
@@ -185,7 +157,7 @@ namespace VNLib.Net.Messaging.FBM.Server
//Clear headers before freeing buffer
_headers.Clear();
//Free header-buffer
- ArrayPool<char>.Shared.Return(_headerBuffer!);
+ ArrayPool<byte>.Shared.Return(_headerBuffer!);
_headerBuffer = null;
ConnectionId = null;
MessageId = 0;
diff --git a/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs b/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs
index ac34dda..9ca6b4d 100644
--- a/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs
+++ b/lib/Net.Messaging.FBM/src/Server/FBMResponseMessage.cs
@@ -52,7 +52,7 @@ namespace VNLib.Net.Messaging.FBM.Server
private readonly ISlindingWindowBuffer<byte> _headerAccumulator;
private readonly Encoding _headerEncoding;
- private IAsyncMessageBody? _messageBody;
+ private IAsyncMessageBody? MessageBody;
///<inheritdoc/>
public int MessageId { get; private set; }
@@ -67,7 +67,7 @@ namespace VNLib.Net.Messaging.FBM.Server
//Release header accumulator
_headerAccumulator.Close();
- _messageBody = null;
+ MessageBody = null;
MessageId = 0;
@@ -83,31 +83,26 @@ namespace VNLib.Net.Messaging.FBM.Server
{
//Reset accumulator when message id is written
_headerAccumulator.Reset();
- //Write the messageid to the begining of the headers buffer
+
MessageId = messageId;
- _headerAccumulator.Append((byte)HeaderCommand.MessageId);
- _headerAccumulator.Append(messageId);
- _headerAccumulator.WriteTermination();
+
+ //Write the messageid to the begining of the headers buffer
+ Helpers.WriteMessageid(_headerAccumulator, messageId);
}
///<inheritdoc/>
- public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value)
- {
- WriteHeader((byte)header, value);
- }
+ public void WriteHeader(HeaderCommand header, ReadOnlySpan<char> value) => WriteHeader((byte)header, value);
+
///<inheritdoc/>
- public void WriteHeader(byte header, ReadOnlySpan<char> value)
- {
- _headerAccumulator.WriteHeader(header, value, _headerEncoding);
- }
-
+ public void WriteHeader(byte header, ReadOnlySpan<char> value) => Helpers.WriteHeader(_headerAccumulator, header, value, _headerEncoding);
+
///<inheritdoc/>
public void WriteBody(ReadOnlySpan<byte> body, ContentType contentType = ContentType.Binary)
{
//Append content type header
WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(contentType));
//end header segment
- _headerAccumulator.WriteTermination();
+ Helpers.WriteTermination(_headerAccumulator);
//Write message body
_headerAccumulator.Append(body);
}
@@ -119,7 +114,9 @@ namespace VNLib.Net.Messaging.FBM.Server
/// <exception cref="InvalidOperationException"></exception>
public void AddMessageBody(IAsyncMessageBody messageBody)
{
- if(_messageBody != null)
+ _ = messageBody ?? throw new ArgumentNullException(nameof(messageBody));
+
+ if(MessageBody != null)
{
throw new InvalidOperationException("The message body is already set");
}
@@ -128,11 +125,10 @@ namespace VNLib.Net.Messaging.FBM.Server
WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(messageBody.ContentType));
//end header segment
- _headerAccumulator.WriteTermination();
+ Helpers.WriteTermination(_headerAccumulator);
//Store message body
- _messageBody = messageBody;
-
+ MessageBody = messageBody;
}
/// <summary>
@@ -143,10 +139,11 @@ namespace VNLib.Net.Messaging.FBM.Server
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)
+ if(MessageBody?.RemainingSize > 0 && _headerAccumulator.RemainingSize > 0)
{
//Read data from the message
- int read = await _messageBody.ReadAsync(_headerAccumulator.RemainingBuffer, cancellationToken);
+ int read = await MessageBody.ReadAsync(_headerAccumulator.RemainingBuffer, cancellationToken);
+
//Advance accumulator to the read bytes
_headerAccumulator.Advance(read);
}
@@ -178,23 +175,23 @@ namespace VNLib.Net.Messaging.FBM.Server
Current = _message._headerAccumulator.AccumulatedBuffer;
//Update data remaining flag
- DataRemaining = _message._messageBody?.RemainingSize > 0;
+ DataRemaining = _message.MessageBody?.RemainingSize > 0;
//Set headers read flag
HeadersRead = true;
return true;
}
- else if (_message._messageBody?.RemainingSize > 0)
+ else if (_message.MessageBody?.RemainingSize > 0)
{
//Use the header buffer as the buffer for the message body
Memory<byte> buffer = _message._headerAccumulator.Buffer;
//Read body segment
- int read = await _message._messageBody.ReadAsync(buffer);
+ int read = await _message.MessageBody.ReadAsync(buffer);
//Update data remaining flag
- DataRemaining = _message._messageBody.RemainingSize > 0;
+ DataRemaining = _message.MessageBody.RemainingSize > 0;
if (read > 0)
{
@@ -206,7 +203,7 @@ namespace VNLib.Net.Messaging.FBM.Server
return false;
}
- public async ValueTask DisposeAsync()
+ public ValueTask DisposeAsync()
{
//Clear current segment
Current = default;
@@ -215,9 +212,13 @@ namespace VNLib.Net.Messaging.FBM.Server
HeadersRead = false;
//Dispose the message body if set
- if (_message._messageBody != null)
+ if (_message.MessageBody != null)
+ {
+ return _message.MessageBody.DisposeAsync();
+ }
+ else
{
- await _message._messageBody.DisposeAsync();
+ return ValueTask.CompletedTask;
}
}
}
diff --git a/lib/Net.Messaging.FBM/src/Server/readme.md b/lib/Net.Messaging.FBM/src/Server/readme.md
index 489e58f..68eb505 100644
--- a/lib/Net.Messaging.FBM/src/Server/readme.md
+++ b/lib/Net.Messaging.FBM/src/Server/readme.md
@@ -14,7 +14,7 @@ messages. Headers are identified by a single byte, followed by a variable length
encoded character sequence, followed by a termination of 0xFF, 0xF1 (may change).
### Message structure
- 4 byte positive (signed 32-bit integer) message id
+ 4 byte positive (big endian signed 32-bit integer) message id
2 byte termination
1 byte header-id
variable length UTF8 value
diff --git a/lib/Plugins.Essentials/src/HttpEntity.cs b/lib/Plugins.Essentials/src/HttpEntity.cs
index 416b004..63e61f7 100644
--- a/lib/Plugins.Essentials/src/HttpEntity.cs
+++ b/lib/Plugins.Essentials/src/HttpEntity.cs
@@ -57,6 +57,7 @@ namespace VNLib.Plugins.Essentials
/// The connection event entity
/// </summary>
private readonly IHttpEvent Entity;
+
public HttpEntity(IHttpEvent entity, EventProcessor root, in SessionHandle session, in CancellationToken cancellation)
{
Entity = entity;
diff --git a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs
index 6a974e0..fa7f8b7 100644
--- a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs
+++ b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs
@@ -138,7 +138,7 @@ namespace VNLib.Plugins.Essentials.Sessions
/// Gets or sets the user-id for the current session.
/// </para>
/// <para>
- /// Login code usually sets this value and it should be read-only
+ /// Login routines usually set this value and it should be read-only
/// </para>
/// </summary>
public readonly string UserID
diff --git a/lib/Plugins.PluginBase/src/VLogProvider.cs b/lib/Plugins.PluginBase/src/VLogProvider.cs
index d95725f..4ec05c2 100644
--- a/lib/Plugins.PluginBase/src/VLogProvider.cs
+++ b/lib/Plugins.PluginBase/src/VLogProvider.cs
@@ -24,6 +24,7 @@
using System;
using System.Linq;
+using System.Runtime.CompilerServices;
using Serilog;
using Serilog.Core;
@@ -46,21 +47,31 @@ namespace VNLib.Plugins
/// </summary>
/// <param name="config">Configuration to generate the logger from</param>
public VLogProvider(LoggerConfiguration config) => LogCore = config.CreateLogger();
+
///<inheritdoc/>
- public void Flush() { }
+ public void Flush() {}
+
///<inheritdoc/>
public object GetLogProvider() => LogCore;
+
+ ///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsEnabled(LogLevel level) => LogCore.IsEnabled((LogEventLevel)level);
+
///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(LogLevel level, string value)
{
LogCore.Write((LogEventLevel)level, value);
}
///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(LogLevel level, Exception exception, string value = "")
{
LogCore.Write((LogEventLevel)level, exception, value);
}
///<inheritdoc/>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(LogLevel level, string value, params object[] args)
{
LogCore.Write((LogEventLevel)level, value, args);
diff --git a/lib/Utils/src/Logging/ILogProvider.cs b/lib/Utils/src/Logging/ILogProvider.cs
index 55dbd6f..3fce8b6 100644
--- a/lib/Utils/src/Logging/ILogProvider.cs
+++ b/lib/Utils/src/Logging/ILogProvider.cs
@@ -70,6 +70,14 @@ namespace VNLib.Utils.Logging
/// </summary>
/// <returns>The underlying log source</returns>
object GetLogProvider();
+
+ /// <summary>
+ /// Checks if the desired log level is enabled.
+ /// </summary>
+ /// <param name="level">The log level to check</param>
+ /// <returns>True if the log level is available to write logs to, false if the level is not available to write to</returns>
+ bool IsEnabled(LogLevel level);
+
/// <summary>
/// Gets the underlying log source
/// </summary>
diff --git a/lib/Utils/src/Memory/Caching/LRUCache.cs b/lib/Utils/src/Memory/Caching/LRUCache.cs
index 7e96e0a..6cbb425 100644
--- a/lib/Utils/src/Memory/Caching/LRUCache.cs
+++ b/lib/Utils/src/Memory/Caching/LRUCache.cs
@@ -56,17 +56,17 @@ namespace VNLib.Utils.Memory.Caching
/// Adds a new record to the LRU cache
/// </summary>
/// <param name="item">A <see cref="KeyValuePair{TKey, TValue}"/> to add to the cache store</param>
- public override void Add(KeyValuePair<TKey, TValue> item)
+ public override void Add(in KeyValuePair<TKey, TValue> item)
{
//See if the store is at max capacity and an item needs to be evicted
- if(Count == MaxCapacity)
+ if (Count == MaxCapacity)
{
//A record needs to be evicted before a new record can be added
//Get the oldest node from the list to reuse its instance and remove the old value
LinkedListNode<KeyValuePair<TKey, TValue>> oldNode = List.First!; //not null because count is at max capacity so an item must be at the end of the list
//Store old node value field
- KeyValuePair<TKey, TValue> oldRecord = oldNode.Value;
+ ref KeyValuePair<TKey, TValue> oldRecord = ref oldNode.ValueRef;
//Remove from lookup
LookupTable.Remove(oldRecord.Key);
//Remove the node
@@ -78,14 +78,15 @@ namespace VNLib.Utils.Memory.Caching
//Add to end of list
List.AddLast(oldNode);
//Invoke evicted method
- Evicted(oldRecord);
+ Evicted(ref oldRecord);
}
else
{
//Add new item to the list
- base.Add(item);
+ base.Add(in item);
}
}
+
/// <summary>
/// Attempts to get a value by the given key.
/// </summary>
@@ -115,7 +116,7 @@ namespace VNLib.Utils.Memory.Caching
/// Invoked when a record is evicted from the cache
/// </summary>
/// <param name="evicted">The record that is being evicted</param>
- protected abstract void Evicted(KeyValuePair<TKey, TValue> evicted);
+ protected abstract void Evicted(ref KeyValuePair<TKey, TValue> evicted);
/// <summary>
/// Invoked when an entry was requested and was not found in cache.
/// </summary>
diff --git a/lib/Utils/src/Memory/Caching/LRUDataStore.cs b/lib/Utils/src/Memory/Caching/LRUDataStore.cs
index f564fcc..89d7c12 100644
--- a/lib/Utils/src/Memory/Caching/LRUDataStore.cs
+++ b/lib/Utils/src/Memory/Caching/LRUDataStore.cs
@@ -44,7 +44,7 @@ namespace VNLib.Utils.Memory.Caching
protected Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> LookupTable { get; }
/// <summary>
/// A linked list that tracks the least recently used item.
- /// New items (or recently access items) are moved to the end of the list.
+ /// New items (or recently accessed items) are moved to the end of the list.
/// The head contains the least recently used item
/// </summary>
protected LinkedList<KeyValuePair<TKey, TValue>> List { get; }
@@ -57,6 +57,7 @@ namespace VNLib.Utils.Memory.Caching
LookupTable = new();
List = new();
}
+
/// <summary>
/// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and sets
/// the lookup table's inital capacity
@@ -67,6 +68,7 @@ namespace VNLib.Utils.Memory.Caching
LookupTable = new(initialCapacity);
List = new();
}
+
/// <summary>
/// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and uses the
/// specified keycomparison
@@ -77,6 +79,7 @@ namespace VNLib.Utils.Memory.Caching
LookupTable = new(keyComparer);
List = new();
}
+
/// <summary>
/// Initializes an empty <see cref="LRUDataStore{TKey, TValue}"/> and uses the
/// specified keycomparison, and sets the lookup table's initial capacity
@@ -110,8 +113,11 @@ namespace VNLib.Utils.Memory.Caching
{
//Remove the node before re-adding it
List.Remove(oldNode);
- oldNode.Value = new KeyValuePair<TKey, TValue>(key, value);
- //Move the item to the front of the list
+
+ //Reuse the node
+ oldNode.ValueRef = new KeyValuePair<TKey, TValue>(key, value);
+
+ //Move the item to the back of the list
List.AddLast(oldNode);
}
else
@@ -123,9 +129,12 @@ namespace VNLib.Utils.Memory.Caching
}
///<inheritdoc/>
public ICollection<TKey> Keys => LookupTable.Keys;
- ///<inheritdoc/>
+
+ ///<summary>
+ /// Not supported
+ /// </summary>
///<exception cref="NotImplementedException"></exception>
- public ICollection<TValue> Values => throw new NotImplementedException();
+ public virtual ICollection<TValue> Values => throw new NotSupportedException("Values are not stored in an independent collection, as they are not directly mutable");
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => LookupTable.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => List.Select(static node => node.Value);
IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() => List.Select(static node => node.Value).GetEnumerator();
@@ -134,6 +143,7 @@ namespace VNLib.Utils.Memory.Caching
/// Gets the number of items within the LRU store
/// </summary>
public int Count => List.Count;
+
///<inheritdoc/>
public abstract bool IsReadOnly { get; }
@@ -147,10 +157,11 @@ namespace VNLib.Utils.Memory.Caching
//Create new kvp lookup ref
KeyValuePair<TKey, TValue> lookupRef = new(key, value);
//Insert the lookup
- Add(lookupRef);
+ Add(in lookupRef);
}
+
///<inheritdoc/>
- public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
+ public bool Remove(in KeyValuePair<TKey, TValue> item) => Remove(item.Key);
///<inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator();
///<inheritdoc/>
@@ -164,7 +175,7 @@ namespace VNLib.Utils.Memory.Caching
/// Adds the specified record to the store and places it at the end of the LRU queue
/// </summary>
/// <param name="item">The item to add</param>
- public virtual void Add(KeyValuePair<TKey, TValue> item)
+ public virtual void Add(in KeyValuePair<TKey, TValue> item)
{
//Init new ll node
LinkedListNode<KeyValuePair<TKey, TValue>> newNode = new(item);
@@ -173,6 +184,7 @@ namespace VNLib.Utils.Memory.Caching
//Add to the end of the linked list
List.AddLast(newNode);
}
+
/// <summary>
/// Removes all elements from the LRU store
/// </summary>
@@ -187,7 +199,7 @@ namespace VNLib.Utils.Memory.Caching
/// </summary>
/// <param name="item">The record to search for</param>
/// <returns>True if the key was found in the store and the value equals the stored value, false otherwise</returns>
- public virtual bool Contains(KeyValuePair<TKey, TValue> item)
+ public virtual bool Contains(in KeyValuePair<TKey, TValue> item)
{
if (LookupTable.TryGetValue(item.Key, out LinkedListNode<KeyValuePair<TKey, TValue>>? lookup))
{
@@ -227,6 +239,17 @@ namespace VNLib.Utils.Memory.Caching
value = default;
return false;
}
-
+
+ /// <summary>
+ /// Adds the specified record to the store and places it at the end of the LRU queue
+ /// </summary>
+ /// <param name="item">The item to add</param>
+ public virtual void Add(KeyValuePair<TKey, TValue> item) => Add(in item);
+
+ ///<inheritdoc/>
+ public virtual bool Contains(KeyValuePair<TKey, TValue> item) => Contains(in item);
+
+ ///<inheritdoc/>
+ public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(in item);
}
}
diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs
index df2792b..7a6b4ef 100644
--- a/lib/Utils/src/Memory/MemoryHandle.cs
+++ b/lib/Utils/src/Memory/MemoryHandle.cs
@@ -92,7 +92,7 @@ namespace VNLib.Utils.Memory
/// Number of bytes allocated to the current instance
/// </summary>
/// <exception cref="OverflowException"></exception>
- public unsafe ulong ByteLength
+ public nuint ByteLength
{
//Check for overflows when converting to bytes (should run out of memory before this is an issue, but just incase)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index 56ccb7e..2d51d2f 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -588,6 +588,39 @@ namespace VNLib.Utils.Memory
#region alloc
+
+ /// <summary>
+ /// Rounds the requested byte size up to the nearest page
+ /// number of bytes
+ /// </summary>
+ /// <param name="byteSize">The number of bytes to get the rounded page size of</param>
+ /// <returns>The number of bytes equivalent to the requested byte size rounded to the next system memory page</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nuint NearestPage(nuint byteSize)
+ {
+ //Get page count by dividing count by number of pages
+ nuint pages = (uint)Math.Ceiling(byteSize / (double)Environment.SystemPageSize);
+
+ //Multiply back to page sizes
+ return pages * (nuint)Environment.SystemPageSize;
+ }
+
+ /// <summary>
+ /// Rounds the requested byte size up to the nearest page
+ /// number of bytes
+ /// </summary>
+ /// <param name="byteSize">The number of bytes to get the rounded page size of</param>
+ /// <returns>The number of bytes equivalent to the requested byte size rounded to the next system memory page</returns>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static nint NearestPage(nint byteSize)
+ {
+ //Get page count by dividing count by number of pages
+ nint pages = (int)Math.Ceiling(byteSize / (double)Environment.SystemPageSize);
+
+ //Multiply back to page sizes
+ return pages * Environment.SystemPageSize;
+ }
+
/// <summary>
/// Allocates a block of unmanaged, or pooled manaaged memory depending on
/// compilation flags and runtime unamanged allocators.
diff --git a/lib/Utils/tests/VNLib.UtilsTests.csproj b/lib/Utils/tests/VNLib.UtilsTests.csproj
index 89b3124..3a079c6 100644
--- a/lib/Utils/tests/VNLib.UtilsTests.csproj
+++ b/lib/Utils/tests/VNLib.UtilsTests.csproj
@@ -20,9 +20,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
- <PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
- <PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
+ <PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
+ <PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>