From a5d88f2cf08ea3aad2c8802bdc416e7b40c0f204 Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 27 Jan 2023 21:13:05 -0500 Subject: Object cache overhaul and logger updates --- .../src/IdentityUtility/JsonWebToken.cs | 30 ++-- lib/Net.Http/src/Core/Response/ChunkedStream.cs | 2 +- lib/Net.Http/src/Core/SharedHeaderReaderBuffer.cs | 5 +- .../src/Client/ClientExtensions.cs | 9 ++ lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs | 169 +++++++++++++++++++ lib/Net.Messaging.FBM/src/Client/FBMClient.cs | 17 +- .../src/Client/FBMClientConfig.cs | 5 +- lib/Net.Messaging.FBM/src/Client/FBMRequest.cs | 180 +++++++++------------ lib/Net.Messaging.FBM/src/Client/FBMResponse.cs | 6 +- lib/Net.Messaging.FBM/src/Client/Helpers.cs | 138 ++++++++++------ lib/Net.Messaging.FBM/src/Client/README.md | 5 +- lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs | 60 +++++++ lib/Net.Messaging.FBM/src/FBMMessageHeader.cs | 94 +++++++++++ lib/Net.Messaging.FBM/src/Server/FBMContext.cs | 13 +- lib/Net.Messaging.FBM/src/Server/FBMListener.cs | 7 +- .../src/Server/FBMListenerSessionParams.cs | 2 +- .../src/Server/FBMRequestMessage.cs | 60 ++----- .../src/Server/FBMResponseMessage.cs | 59 +++---- lib/Net.Messaging.FBM/src/Server/readme.md | 2 +- lib/Plugins.Essentials/src/HttpEntity.cs | 1 + lib/Plugins.Essentials/src/Sessions/SessionInfo.cs | 2 +- lib/Plugins.PluginBase/src/VLogProvider.cs | 13 +- lib/Utils/src/Logging/ILogProvider.cs | 8 + lib/Utils/src/Memory/Caching/LRUCache.cs | 13 +- lib/Utils/src/Memory/Caching/LRUDataStore.cs | 43 +++-- lib/Utils/src/Memory/MemoryHandle.cs | 2 +- lib/Utils/src/Memory/MemoryUtil.cs | 33 ++++ lib/Utils/tests/VNLib.UtilsTests.csproj | 6 +- 28 files changed, 701 insertions(+), 283 deletions(-) create mode 100644 lib/Net.Messaging.FBM/src/Client/FBMBuffer.cs create mode 100644 lib/Net.Messaging.FBM/src/FBMHeaderBuffer.cs create mode 100644 lib/Net.Messaging.FBM/src/FBMMessageHeader.cs (limited to 'lib') 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 /// /// Parses a JWT from a Base64URL encoded character buffer /// - /// + /// The JWT characters to decode /// An optional instance to alloc buffers from + /// The encoding used to decode the text to binary /// The parses /// /// /// - public static JsonWebToken Parse(ReadOnlySpan urlEncJwtString, IUnmangedHeap? heap = null) + public static JsonWebToken Parse(ReadOnlySpan 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 binBuffer = heap.Alloc(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 buffer = jwt.DataBuffer; + ForwardOnlyReader 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. /// - public int ByteSize => (int)DataStream.Position; + public int ByteSize => Convert.ToInt32(DataStream.Position); /// /// 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 /// /// /// + /// [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 +{ + /// + /// Represents a shared internal character and bianry buffer for + /// + internal sealed class FBMBuffer : IDisposable + { + private readonly IMemoryOwner Handle; + + private readonly BufferWriter _writer; + private readonly BinaryRequestAccumulator _requestAccumulator; + + + internal FBMBuffer(IMemoryOwner handle) + { + Handle = handle; + _writer = new(this); + _requestAccumulator = new(handle.Memory); + } + + + /// + /// Gets the internal request data accumulator + /// + public IDataAccumulator RequestBuffer => _requestAccumulator; + + /// + /// Gets the accumulated request data for reading + /// + /// The accumulated request data as memory + public ReadOnlyMemory RequestData => _requestAccumulator.AccumulatedMemory; + + /// + /// Completes the header segment and prepares the body writer + /// + /// A for writing an FBM message body to + public IBufferWriter GetBodyWriter() + { + //complete the header segments by writing an empty line + Helpers.WriteTermination(RequestBuffer); + + //Return the internal writer + return _writer; + } + + /// + /// Gets the buffer manager for managing response headers + /// + /// The for managing response header buffers + public FBMHeaderBuffer GetResponseHeaderBuffer() + { + //Get a buffer wrapper around the memory handle + return new FBMHeaderBuffer(Handle.Memory); + } + + public void Dispose() + { + //Dispose handle + Handle.Dispose(); + } + + /// + /// Resets the request accumulator and writes the initial message id + /// + /// The message id + 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 + { + private readonly int Size; + private readonly Memory Buffer; + + internal BinaryRequestAccumulator(Memory buffer) + { + Buffer = buffer; + Size = buffer.Length; + } + + /// + public int AccumulatedSize { get; private set; } + + /// + public int RemainingSize => Size - AccumulatedSize; + + /// + public Span Remaining => Buffer.Span.Slice(AccumulatedSize, RemainingSize); + /// + public Span Accumulated => Buffer.Span[..AccumulatedSize]; + + /// + /// Gets the accumulated data as a memory segment + /// + public Memory AccumulatedMemory => Buffer[..AccumulatedSize]; + + /// + /// Gets the remaining buffer segment as a memory segment + /// + public Memory RemainingMemory => Buffer.Slice(AccumulatedSize, RemainingSize); + + /// + public void Advance(int count) => AccumulatedSize += count; + /// + public void Reset() => AccumulatedSize = 0; + } + + private sealed class BufferWriter : IBufferWriter + { + private readonly FBMBuffer Buffer; + + public BufferWriter(FBMBuffer buffer) + { + Buffer = buffer; + } + + public void Advance(int count) + { + //Advance the writer + Buffer.RequestBuffer.Advance(count); + } + + public Memory GetMemory(int sizeHint = 0) + { + //Get the remaining memory segment + return Buffer._requestAccumulator.RemainingMemory; + } + + public Span 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 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 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.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 { /// - /// 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 /// public readonly int RecvBufferSize { get; init; } /// @@ -48,7 +49,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// public readonly int MessageBufferSize { get; init; } /// - /// The size (in chars) of the client/server message header buffer + /// The size (in bytes) of the client/server message header buffer /// public readonly int MaxHeaderBufferSize { get; init; } /// 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 { + /// + /// /// A reusable Fixed Buffer Message request container. This class is not thread-safe + /// + /// + /// The internal buffer is used for storing headers, body data (unless streaming) + /// /// public sealed class FBMRequest : VnDisposeable, IReusable, IFBMMessage, IStringSerializeable { - private sealed class BufferWriter : IBufferWriter - { - private readonly FBMRequest _request; - - public BufferWriter(FBMRequest request) - { - _request = request; - } - - public void Advance(int count) - { - _request.Position += count; - } - - public Memory GetMemory(int sizeHint = 0) - { - return sizeHint > 0 ? _request.RemainingBuffer[0..sizeHint] : _request.RemainingBuffer; - } - - public Span GetSpan(int sizeHint = 0) - { - return sizeHint > 0 ? _request.RemainingBuffer.Span[0..sizeHint] : _request.RemainingBuffer.Span; - } - } - - private readonly IMemoryOwner 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>> ResponseHeaderList = new(); - private char[]? ResponseHeaderBuffer; + + private readonly List ResponseHeaderList = new(); /// /// The size (in bytes) of the request message /// - public int Length => Position; - private Memory RemainingBuffer => HeapBuffer.Memory[Position..]; + public int Length => Buffer.RequestBuffer.AccumulatedSize; /// /// The id of the current request message /// public int MessageId { get; } - /// - /// The request message packet - /// - public ReadOnlyMemory RequestData => HeapBuffer.Memory[..Position]; + /// /// An 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; } + /// /// Initializes a new with the sepcified message buffer size, /// and a random messageid @@ -107,77 +82,71 @@ namespace VNLib.Net.Messaging.FBM.Client /// The fbm client config storing required config variables public FBMRequest(in FBMClientConfig config) : this(Helpers.RandomMessageId, in config) { } + /// /// Initializes a new with the sepcified message buffer size and a custom MessageId /// /// The custom message id /// The fbm client config storing required config variables 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(config.MessageBufferSize); - - MessageId = messageId; - - HeaderEncoding = config.HeaderEncoding; - ResponseHeaderBufferSize = config.MaxHeaderBufferSize; + :this(messageId, config.BufferHeap, config.MessageBufferSize, config.HeaderEncoding) + { } - WriteMessageId(); - _writer = new(this); - } /// - /// Resets the internal buffer and writes the message-id header to the begining - /// of the buffer + /// Initializes a new with the sepcified message buffer size and a custom MessageId /// - private void WriteMessageId() + /// The custom message id + /// The heap to allocate the internal buffer from + /// The size of the internal buffer + /// The encoding instance used for header character encoding + public FBMRequest(int messageId, IUnmangedHeap heap, int bufferSize, Encoding headerEncoding) { - //Get writer over buffer - ForwardOnlyWriter 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 HeapBuffer = heap.DirectAlloc(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(); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteHeader(HeaderCommand header, ReadOnlySpan value) => WriteHeader((byte)header, value); /// - public void WriteHeader(byte header, ReadOnlySpan value) - { - ForwardOnlyWriter buffer = new(RemainingBuffer.Span); - buffer.WriteHeader(header, value, Helpers.DefaultEncoding); - //Update position - Position += buffer.Written; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteHeader(byte header, ReadOnlySpan value) => Helpers.WriteHeader(Buffer.RequestBuffer, header, value, Helpers.DefaultEncoding); /// public void WriteBody(ReadOnlySpan body, ContentType contentType = ContentType.Binary) { //Write content type header WriteHeader(HeaderCommand.ContentType, HttpHelpers.GetContentTypeString(contentType)); - //Get writer over buffer - ForwardOnlyWriter buffer = new(RemainingBuffer.Span); //Now safe to write body - buffer.WriteBody(body); - //Update position - Position += buffer.Written; + Helpers.WriteBody(Buffer.RequestBuffer, body); } + /// /// Returns buffer writer for writing the body data to the internal message buffer /// - /// A to write message body to - public IBufferWriter GetBodyWriter() + /// A to write message body to + /// Calling this method ends the headers section of the request + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IBufferWriter GetBodyWriter() => Buffer.GetBodyWriter(); + + + /// + /// The request message packet, this may cause side effects + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory GetRequestData() { - //Write body termination - Helpers.Termination.CopyTo(RemainingBuffer); - Position += Helpers.Termination.Length; - //Return buffer writer - return _writer; + return Buffer.RequestData; } /// @@ -186,8 +155,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// 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 /// 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 + /// /// Gets the response of the sent message /// @@ -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.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.Shared.Return(ResponseHeaderBuffer!); - ResponseHeaderBuffer = null; - } } + #endregion + + #region Diagnostics /// public string Compile() { - int charSize = Helpers.DefaultEncoding.GetCharCount(RequestData.Span); + ReadOnlyMemory requestData = GetRequestData(); + + int charSize = Helpers.DefaultEncoding.GetCharCount(requestData.Span); using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(charSize + 128); @@ -286,10 +256,11 @@ namespace VNLib.Net.Messaging.FBM.Client /// public void Compile(ref ForwardOnlyWriter writer) { + ReadOnlyMemory 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); } /// public ERRNO Compile(in Span buffer) @@ -300,6 +271,7 @@ namespace VNLib.Net.Messaging.FBM.Client } /// 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 /// /// A collection of response message headers /// - public readonly IReadOnlyList>> Headers { get; } + public readonly IReadOnlyList Headers { get; } /// /// Status flags of the message parse operation /// @@ -66,7 +66,7 @@ namespace VNLib.Net.Messaging.FBM.Client /// The size of the buffer to alloc for header value storage /// The collection of headerse /// A method that will be invoked when the message response body is disposed - public FBMResponse(VnMemoryStream? vms, HeaderParseError status, IReadOnlyList>> headerList, Action onDispose) + public FBMResponse(VnMemoryStream? vms, HeaderParseError status, IReadOnlyList 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>>(); + Headers = Array.Empty(); 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 { /// @@ -46,8 +47,15 @@ namespace VNLib.Net.Messaging.FBM /// public const int CONTROL_FRAME_MID = -500; - public static readonly Encoding DefaultEncoding = Encoding.UTF8; - public static readonly ReadOnlyMemory Termination = new byte[] { 0xFF, 0xF1 }; + /// + /// Gets the default header character encoding instance + /// + public static Encoding DefaultEncoding { get; } = Encoding.UTF8; + + /// + /// The FBM protocol header line termination symbols + /// + public static ReadOnlyMemory Termination { get; } = new byte[] { 0xFF, 0xF1 }; /// /// 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 messageIdSegment = line.Slice(1, sizeof(int)); + //get the messageid from the messageid segment - return BitConverter.ToInt32(messageIdSegment); + return BinaryPrimitives.ReadInt32BigEndian(messageIdSegment); } + /// + /// Appends the message id header to the accumulator + /// + /// The accumulatore to write the message id to + /// The message id to write to the accumulator + public static void WriteMessageid(IDataAccumulator accumulator, int messageid) + { + //Alloc buffer for message id + the message id header + Span 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); + } + + /// /// Alloctes a random integer to use as a message id /// public static int RandomMessageId => RandomNumberGenerator.GetInt32(1, int.MaxValue); - + /// /// Gets the remaining data after the current position of the stream. /// /// The stream to segment /// The remaining data segment + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ReadOnlySpan 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]; } + /// /// Parses headers from the request stream, stores headers from the buffer into the /// header collection @@ -118,24 +155,31 @@ namespace VNLib.Net.Messaging.FBM /// The collection to store headers in /// The encoding type used to deocde header values /// The results of the parse operation - public static HeaderParseError ParseHeaders(VnMemoryStream vms, char[] buffer, ICollection>> headers, Encoding encoding) + internal static HeaderParseError ParseHeaders(VnMemoryStream vms, in FBMHeaderBuffer buffer, ICollection headers, Encoding encoding) { HeaderParseError status = HeaderParseError.None; - //sliding window - Memory currentWindow = buffer; + + //Get a sliding window writer over the enitre buffer + ForwardOnlyWriter writer = new(buffer.GetSpan()); + //Accumulate headers while (true) { //Read the next line from the current stream ReadOnlySpan 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 /// /// /// The enum value from hte first byte of the message + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HeaderCommand GetHeaderCommand(ReadOnlySpan line) { return (HeaderCommand)line[0]; } + /// /// 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 /// The message header line to get the value of /// The output character buffer to write characters to /// The encoding to decode the specified data with - /// The number of characters encoded + /// The number of characters encoded or -1 if the output buffer is too small public static ERRNO GetHeaderValue(ReadOnlySpan line, Span 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; } - - /// - /// Appends an arbitrary header to the current request buffer - /// - /// - /// The of the header - /// The value of the header - /// Encoding to use when writing character message - /// - public static void WriteHeader(ref this ForwardOnlyWriter buffer, byte header, ReadOnlySpan 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(); - } + /// /// Ends the header section of the request and appends the message body to @@ -223,32 +249,38 @@ namespace VNLib.Net.Messaging.FBM /// /// The message body to send with request /// - public static void WriteBody(ref this ForwardOnlyWriter buffer, ReadOnlySpan body) + public static void WriteBody(IDataAccumulator buffer, ReadOnlySpan body) { //start with termination - buffer.WriteTermination(); + WriteTermination(buffer); //Write the body buffer.Append(body); } + + /// - /// Writes a line termination to the message buffer + /// Rounds the requested byte size up to the 1kb + /// number of bytes /// - /// - public static void WriteTermination(ref this ForwardOnlyWriter buffer) + /// The number of bytes to get the rounded 1kb size of + /// The number of bytes equivalent to the requested byte size rounded to the next 1kb size + [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; } + /// /// Writes a line termination to the message buffer /// /// - public static void WriteTermination(this IDataAccumulator buffer) - { - //write termination - buffer.Append(Termination.Span); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteTermination(IDataAccumulator buffer) => buffer.Append(Termination.Span); /// /// Appends an arbitrary header to the current request buffer @@ -258,7 +290,7 @@ namespace VNLib.Net.Messaging.FBM /// The value of the header /// Encoding to use when writing character message /// - public static void WriteHeader(this IDataAccumulator buffer, byte header, ReadOnlySpan value, Encoding encoding) + public static void WriteHeader(IDataAccumulator buffer, byte header, ReadOnlySpan 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 _handle; + + internal FBMHeaderBuffer(Memory handle) => _handle = handle; + + /// + /// Gets a character squence within the binary buffer of the specified + /// character offset and length + /// + /// The character offset within the internal buffer + /// The number of characters within the desired span + /// A span at the given character offset and of the specified length + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan(int offset, int count) + { + //Get the character span + Span span = GetSpan(); + return span.Slice(offset, count); + } + + /// + /// Gets the entire internal buffer as a character span + /// + /// A span over the entire internal buffer + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetSpan() => MemoryMarshal.Cast(_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 +{ + /// + /// Represents a Key-Value pair FBM response message header + /// + public readonly struct FBMMessageHeader : IEquatable + { + private readonly FBMHeaderBuffer _buffer; + private readonly int _headerOffset; + private readonly int _headerLength; + + /// + /// The header value or command + /// + public readonly HeaderCommand Header { get; } + + /// + /// The header value + /// + public readonly ReadOnlySpan Value => _buffer.GetSpan(_headerOffset, _headerLength); + + /// + /// Gets the header value as a + /// + /// The allocates string of the value + public readonly string GetValueString() => Value.ToString(); + + /// + /// Initializes a new of the sepcified command + /// that utilizes memory from the specified buffer + /// + /// The buffer that owns the memory our header is stored in + /// The command this header represents + /// The char offset within the buffer our header begins + /// The character length of our header value + internal FBMMessageHeader(FBMHeaderBuffer buffer, HeaderCommand command, int offset, int length) + { + Header = command; + _buffer = buffer; + _headerLength = length; + _headerOffset = offset; + } + + /// + public override bool Equals(object? obj) => obj is FBMMessageHeader other && Equals(other); + + /// + /// Calculates a hash code from the parameter + /// using original string hashcode computation + /// + /// The unique hashcode for the character sequence + public override int GetHashCode() => string.GetHashCode(Value, StringComparison.Ordinal); + + /// + public static bool operator ==(FBMMessageHeader left, FBMMessageHeader right) => left.Equals(right); + /// + public static bool operator !=(FBMMessageHeader left, FBMMessageHeader right) => !(left == right); + + /// + /// Determines if the other response header is equal to the current header by + /// comparing its command and its value + /// + /// The other header to compare + /// True if both headers have the same commad and value sequence + public bool Equals(FBMMessageHeader other) => Header == other.Header && Value.SequenceEqual(other.Value); + } +} 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; /// /// The request message to process @@ -54,8 +57,8 @@ namespace VNLib.Net.Messaging.FBM.Server /// The message header encoding instance 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 /// public readonly int RecvBufferSize { get; init; } /// - /// The size of the character buffer to store FBMheader values in + /// The size of the buffer to store values in /// the /// 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 /// public sealed class FBMRequestMessage : IReusable { - private readonly List>> _headers; - private readonly int HeaderCharBufferSize; + private readonly List _headers; + private readonly int HeaderBufferSize; + /// /// Creates a new resusable /// /// The size of the buffer to alloc during initialization internal FBMRequestMessage(int headerBufferSize) { - HeaderCharBufferSize = headerBufferSize; + HeaderBufferSize = headerBufferSize; _headers = new(); } - private char[]? _headerBuffer; + private byte[]? _headerBuffer; /// /// The ID of the current message @@ -70,7 +69,7 @@ namespace VNLib.Net.Messaging.FBM.Server /// /// A collection of headers for the current request /// - public IReadOnlyList>> Headers => _headers; + public IReadOnlyList Headers => _headers; /// /// Status flags set during the message parsing /// @@ -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 writer = new(_headerBuffer); - - //Accumulate headers - while (true) - { - //Read the next line from the current stream - ReadOnlySpan 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); } /// @@ -158,6 +129,7 @@ namespace VNLib.Net.Messaging.FBM.Server { return BodyData.IsEmpty ? default : BodyData.AsJsonObject(jso); } + /// /// Gets a of the request body /// @@ -173,7 +145,7 @@ namespace VNLib.Net.Messaging.FBM.Server { ParseStatus = HeaderParseError.None; //Alloc header buffer - _headerBuffer = ArrayPool.Shared.Rent(HeaderCharBufferSize); + _headerBuffer = ArrayPool.Shared.Rent(HeaderBufferSize); } @@ -185,7 +157,7 @@ namespace VNLib.Net.Messaging.FBM.Server //Clear headers before freeing buffer _headers.Clear(); //Free header-buffer - ArrayPool.Shared.Return(_headerBuffer!); + ArrayPool.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 _headerAccumulator; private readonly Encoding _headerEncoding; - private IAsyncMessageBody? _messageBody; + private IAsyncMessageBody? MessageBody; /// 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); } /// - public void WriteHeader(HeaderCommand header, ReadOnlySpan value) - { - WriteHeader((byte)header, value); - } + public void WriteHeader(HeaderCommand header, ReadOnlySpan value) => WriteHeader((byte)header, value); + /// - public void WriteHeader(byte header, ReadOnlySpan value) - { - _headerAccumulator.WriteHeader(header, value, _headerEncoding); - } - + public void WriteHeader(byte header, ReadOnlySpan value) => Helpers.WriteHeader(_headerAccumulator, header, value, _headerEncoding); + /// public void WriteBody(ReadOnlySpan 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 /// 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; } /// @@ -143,10 +139,11 @@ namespace VNLib.Net.Messaging.FBM.Server internal async ValueTask 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 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 /// 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. /// /// - /// Login code usually sets this value and it should be read-only + /// Login routines usually set this value and it should be read-only /// /// 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 /// /// Configuration to generate the logger from public VLogProvider(LoggerConfiguration config) => LogCore = config.CreateLogger(); + /// - public void Flush() { } + public void Flush() {} + /// public object GetLogProvider() => LogCore; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEnabled(LogLevel level) => LogCore.IsEnabled((LogEventLevel)level); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(LogLevel level, string value) { LogCore.Write((LogEventLevel)level, value); } /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(LogLevel level, Exception exception, string value = "") { LogCore.Write((LogEventLevel)level, exception, value); } /// + [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 /// /// The underlying log source object GetLogProvider(); + + /// + /// Checks if the desired log level is enabled. + /// + /// The log level to check + /// True if the log level is available to write logs to, false if the level is not available to write to + bool IsEnabled(LogLevel level); + /// /// Gets the underlying log source /// 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 /// /// A to add to the cache store - public override void Add(KeyValuePair item) + public override void Add(in KeyValuePair 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> 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 oldRecord = oldNode.Value; + ref KeyValuePair 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); } } + /// /// Attempts to get a value by the given key. /// @@ -115,7 +116,7 @@ namespace VNLib.Utils.Memory.Caching /// Invoked when a record is evicted from the cache /// /// The record that is being evicted - protected abstract void Evicted(KeyValuePair evicted); + protected abstract void Evicted(ref KeyValuePair evicted); /// /// Invoked when an entry was requested and was not found in cache. /// 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>> LookupTable { get; } /// /// 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 /// protected LinkedList> List { get; } @@ -57,6 +57,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable = new(); List = new(); } + /// /// Initializes an empty and sets /// the lookup table's inital capacity @@ -67,6 +68,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable = new(initialCapacity); List = new(); } + /// /// Initializes an empty and uses the /// specified keycomparison @@ -77,6 +79,7 @@ namespace VNLib.Utils.Memory.Caching LookupTable = new(keyComparer); List = new(); } + /// /// Initializes an empty 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(key, value); - //Move the item to the front of the list + + //Reuse the node + oldNode.ValueRef = new KeyValuePair(key, value); + + //Move the item to the back of the list List.AddLast(oldNode); } else @@ -123,9 +129,12 @@ namespace VNLib.Utils.Memory.Caching } /// public ICollection Keys => LookupTable.Keys; - /// + + /// + /// Not supported + /// /// - public ICollection Values => throw new NotImplementedException(); + public virtual ICollection Values => throw new NotSupportedException("Values are not stored in an independent collection, as they are not directly mutable"); IEnumerable IReadOnlyDictionary.Keys => LookupTable.Keys; IEnumerable IReadOnlyDictionary.Values => List.Select(static node => node.Value); IEnumerator IEnumerable.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 /// public int Count => List.Count; + /// public abstract bool IsReadOnly { get; } @@ -147,10 +157,11 @@ namespace VNLib.Utils.Memory.Caching //Create new kvp lookup ref KeyValuePair lookupRef = new(key, value); //Insert the lookup - Add(lookupRef); + Add(in lookupRef); } + /// - public bool Remove(KeyValuePair item) => Remove(item.Key); + public bool Remove(in KeyValuePair item) => Remove(item.Key); /// IEnumerator IEnumerable.GetEnumerator() => List.GetEnumerator(); /// @@ -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 /// /// The item to add - public virtual void Add(KeyValuePair item) + public virtual void Add(in KeyValuePair item) { //Init new ll node LinkedListNode> newNode = new(item); @@ -173,6 +184,7 @@ namespace VNLib.Utils.Memory.Caching //Add to the end of the linked list List.AddLast(newNode); } + /// /// Removes all elements from the LRU store /// @@ -187,7 +199,7 @@ namespace VNLib.Utils.Memory.Caching /// /// The record to search for /// True if the key was found in the store and the value equals the stored value, false otherwise - public virtual bool Contains(KeyValuePair item) + public virtual bool Contains(in KeyValuePair item) { if (LookupTable.TryGetValue(item.Key, out LinkedListNode>? lookup)) { @@ -227,6 +239,17 @@ namespace VNLib.Utils.Memory.Caching value = default; return false; } - + + /// + /// Adds the specified record to the store and places it at the end of the LRU queue + /// + /// The item to add + public virtual void Add(KeyValuePair item) => Add(in item); + + /// + public virtual bool Contains(KeyValuePair item) => Contains(in item); + + /// + public bool Remove(KeyValuePair 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 /// /// - 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 + + /// + /// Rounds the requested byte size up to the nearest page + /// number of bytes + /// + /// The number of bytes to get the rounded page size of + /// The number of bytes equivalent to the requested byte size rounded to the next system memory page + [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; + } + + /// + /// Rounds the requested byte size up to the nearest page + /// number of bytes + /// + /// The number of bytes to get the rounded page size of + /// The number of bytes equivalent to the requested byte size rounded to the next system memory page + [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; + } + /// /// 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 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive -- cgit