aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-06-16 01:12:07 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-06-16 01:12:07 -0400
commit07ddf6738d32127926d07b1366e56d2a2308b53b (patch)
tree02f01a1a15db95fa082a29a0e9d18f62a016579d
parentff15c05a9c3e632c39f3889820fb7d889342b452 (diff)
perf: Absolutely yuge perf boosts
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs16
-rw-r--r--lib/Net.Http/src/Core/HttpEncodedSegment.cs16
-rw-r--r--lib/Net.Http/src/Core/HttpEvent.cs9
-rw-r--r--lib/Net.Http/src/Core/IHttpContextInformation.cs7
-rw-r--r--lib/Net.Http/src/Core/Request/HttpInputStream.cs35
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequest.cs43
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs53
-rw-r--r--lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs89
-rw-r--r--lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs14
-rw-r--r--lib/Net.Http/src/Core/Response/HttpResponse.cs44
-rw-r--r--lib/Net.Http/src/Core/Response/ReusableResponseStream.cs46
-rw-r--r--lib/Net.Http/src/Core/Response/TransportManager.cs99
-rw-r--r--lib/Net.Transport.SimpleTCP/src/ITransportInterface.cs13
-rw-r--r--lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs33
-rw-r--r--lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs54
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs102
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs4
-rw-r--r--lib/Utils/src/Memory/MemoryUtilAlloc.cs378
-rw-r--r--lib/Utils/src/Memory/UnmanagedHeapBase.cs16
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs6
20 files changed, 634 insertions, 443 deletions
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index 8880d49..18aef69 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -23,7 +23,6 @@
*/
using System;
-using System.IO;
using System.Text;
using System.Threading;
using System.Diagnostics;
@@ -81,6 +80,7 @@ namespace VNLib.Net.Http.Core
/// </remarks>
public IAlternateProtocol? AlternateProtocol { get; set; }
+ private readonly TransportManager Transport;
private readonly ManagedHttpCompressor? _compressor;
private ITransportContext? _ctx;
@@ -105,12 +105,14 @@ namespace VNLib.Net.Http.Core
_compressor = null;
}
+ Transport = new();
+
//Init buffer manager, if compression is supported, we need to alloc a buffer for the compressor
Buffers = new(server.Config.BufferConfig, _compressor != null);
- Request = new (this, server.Config.MaxUploadsPerRequest);
+ Request = new (Transport, server.Config.MaxUploadsPerRequest);
- Response = new (this, Buffers);
+ Response = new (this, Transport, Buffers);
ResponseBody = new ResponseWriter();
}
@@ -134,9 +136,7 @@ namespace VNLib.Net.Http.Core
///<inheritdoc/>
public ref readonly HttpEncodedSegment FinalChunkSegment => ref ParentServer.Config.FinalChunkBytes;
-
- ///<inheritdoc/>
- public Stream GetTransport() => _ctx!.ConnectionStream;
+
int _bytesRead;
@@ -212,7 +212,7 @@ namespace VNLib.Net.Http.Core
Buffers.AllocateBuffer(ParentServer.Config.MemoryPool);
//Init new connection
- Response.OnNewConnection(ctx.ConnectionStream);
+ Transport.OnNewConnection(ctx.ConnectionStream);
}
///<inheritdoc/>
@@ -252,6 +252,8 @@ namespace VNLib.Net.Http.Core
bool IReusable.Release()
{
+ Transport.OnRelease();
+
_ctx = null;
AlternateProtocol = null;
diff --git a/lib/Net.Http/src/Core/HttpEncodedSegment.cs b/lib/Net.Http/src/Core/HttpEncodedSegment.cs
index bcb1f3b..cfeb52e 100644
--- a/lib/Net.Http/src/Core/HttpEncodedSegment.cs
+++ b/lib/Net.Http/src/Core/HttpEncodedSegment.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -51,12 +51,15 @@ namespace VNLib.Net.Http.Core
/// Performs a dangerous reference based copy-to (aka memmove)
/// </summary>
/// <param name="output">The output buffer to write the encoded segment to</param>
- internal readonly int DangerousCopyTo(Span<byte> output)
+ /// <param name="offset">Points to the first byte in the buffer to write to</param>
+ internal readonly int DangerousCopyTo(Span<byte> output, int offset)
{
Debug.Assert(output.Length >= Length, "Output span was empty and could not be written to");
+ Debug.Assert(offset >= 0, "Buffer underrun detected");
+ Debug.Assert(offset + Length <= output.Length, "Output span was too small to hold the encoded segment");
//Get reference of output buffer span
- return DangerousCopyTo(ref MemoryMarshal.GetReference(output));
+ return DangerousCopyTo(ref MemoryMarshal.GetReference(output), (nuint)offset);
}
/// <summary>
@@ -73,14 +76,15 @@ namespace VNLib.Net.Http.Core
//Ensure enough space is available
if(offset + Length <= buffer.Size)
{
+ //More efficient to get the offset ref from the buffer directly
ref byte dst = ref buffer.DangerousGetBinRef(offset);
- return DangerousCopyTo(ref dst);
+ return DangerousCopyTo(ref dst, destOffset: 0);
}
throw new ArgumentOutOfRangeException(nameof(offset), "Buffer is too small to hold the encoded segment");
}
- private readonly int DangerousCopyTo(ref byte output)
+ private readonly int DangerousCopyTo(ref byte output, nuint destOffset)
{
Debug.Assert(!Unsafe.IsNullRef(ref output), "Output span was empty and could not be written to");
@@ -88,7 +92,7 @@ namespace VNLib.Net.Http.Core
ref byte src = ref MemoryMarshal.GetArrayDataReference(Buffer);
//Call memmove with the buffer offset and desired length
- MemoryUtil.SmallMemmove(ref src, Offset, ref output, 0, Length);
+ MemoryUtil.SmallMemmove(ref src, Offset, ref output, destOffset, Length);
return Length;
}
diff --git a/lib/Net.Http/src/Core/HttpEvent.cs b/lib/Net.Http/src/Core/HttpEvent.cs
index 37c5ab5..270e7f8 100644
--- a/lib/Net.Http/src/Core/HttpEvent.cs
+++ b/lib/Net.Http/src/Core/HttpEvent.cs
@@ -91,11 +91,11 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (length == 0)
{
+ //Stream is disposed because it is assumed we now own the lifecycle of the stream
stream.Dispose();
return;
}
-
- //Set status code
+
Context.Response.SetStatusCode(code);
//Finally store the stream input
@@ -120,11 +120,11 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (entity.Remaining == 0)
{
+ //Stream is disposed because it is assumed we now own the lifecycle of the stream
entity.Close();
return;
}
-
- //Set status code
+
Context.Response.SetStatusCode(code);
//Store the memory reader input
@@ -150,6 +150,7 @@ namespace VNLib.Net.Http
//If stream is empty, ignore it, the server will default to 0 content length and avoid overhead
if (length == 0)
{
+ //Stream is disposed because it is assumed we now own the lifecycle of the stream
stream.Dispose();
return;
}
diff --git a/lib/Net.Http/src/Core/IHttpContextInformation.cs b/lib/Net.Http/src/Core/IHttpContextInformation.cs
index 38e86b3..cb51e9f 100644
--- a/lib/Net.Http/src/Core/IHttpContextInformation.cs
+++ b/lib/Net.Http/src/Core/IHttpContextInformation.cs
@@ -22,7 +22,6 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
-using System.IO;
using System.Text;
namespace VNLib.Net.Http.Core
@@ -48,11 +47,5 @@ namespace VNLib.Net.Http.Core
/// The current connection's http version
/// </summary>
HttpVersion CurrentVersion { get; }
-
- /// <summary>
- /// Gets the transport stream for the current connection.
- /// </summary>
- /// <returns>The current transport stream</returns>
- Stream GetTransport();
}
} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Request/HttpInputStream.cs b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
index ccaa336..9ad0218 100644
--- a/lib/Net.Http/src/Core/Request/HttpInputStream.cs
+++ b/lib/Net.Http/src/Core/Request/HttpInputStream.cs
@@ -32,13 +32,14 @@ using System.Runtime.CompilerServices;
using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
+using VNLib.Net.Http.Core.Response;
namespace VNLib.Net.Http.Core
{
/// <summary>
/// Specialized stream to allow reading a request entity body with a fixed content length.
/// </summary>
- internal sealed class HttpInputStream(IHttpContextInformation ContextInfo) : Stream
+ internal sealed class HttpInputStream(TransportManager transport) : Stream
{
private StreamState _state;
private InitDataBuffer? _initalData;
@@ -74,7 +75,6 @@ namespace VNLib.Net.Http.Core
internal ref InitDataBuffer? Prepare(long contentLength)
{
_state.ContentLength = contentLength;
- _state.InputStream = ContextInfo.GetTransport();
return ref _initalData;
}
@@ -153,10 +153,8 @@ namespace VNLib.Net.Http.Core
//See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
if (writer.RemainingSize > 0)
{
- //Read from transport
- ERRNO read = _state.InputStream!.Read(writer.Remaining);
+ ERRNO read = transport.Stream!.Read(writer.Remaining);
- //Update writer position
writer.Advance(read);
_state.Position += read;
@@ -189,27 +187,23 @@ namespace VNLib.Net.Http.Core
{
//Read as much as possible from internal buffer
ERRNO read = _initalData.Value.Read(writer.Remaining.Span);
-
- //Advance writer
+
writer.Advance(read);
-
- //Update position
+
_state.Position += read;
}
//See if data is still remaining to be read from transport (reamining size is also the amount of data that can be read)
if (writer.RemainingSize > 0)
- {
- //Read from transport
- int read = await _state.InputStream!.ReadAsync(writer.Remaining, cancellationToken).ConfigureAwait(true);
-
- //Update writer position
+ {
+ int read = await transport.Stream.ReadAsync(writer.Remaining, cancellationToken)
+ .ConfigureAwait(true);
+
writer.Advance(read);
_state.Position += read;
}
-
- //Return number of bytes written to the buffer
+
return writer.Written;
}
@@ -249,13 +243,11 @@ namespace VNLib.Net.Http.Core
while (bytesToRead > 0)
{
//Read data to the discard buffer until reading is completed (read == 0)
- read = await _state.InputStream!.ReadAsync(HttpServer.WriteOnlyScratchBuffer.Slice(0, bytesToRead), CancellationToken.None)
+ read = await transport.Stream!.ReadAsync(HttpServer.WriteOnlyScratchBuffer.Slice(0, bytesToRead), CancellationToken.None)
.ConfigureAwait(true);
-
- //Update position
+
_state.Position += read;
-
- //Recalculate the number of bytes to read
+
bytesToRead = (int)Math.Min(HttpServer.WriteOnlyScratchBuffer.Length, Remaining);
}
}
@@ -286,7 +278,6 @@ namespace VNLib.Net.Http.Core
private struct StreamState
{
- public Stream? InputStream;
public long Position;
public long ContentLength;
}
diff --git a/lib/Net.Http/src/Core/Request/HttpRequest.cs b/lib/Net.Http/src/Core/Request/HttpRequest.cs
index 2c9eed0..9263e0f 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequest.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequest.cs
@@ -29,10 +29,11 @@ using System.Runtime.CompilerServices;
using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
+using VNLib.Net.Http.Core.Response;
namespace VNLib.Net.Http.Core
{
- internal sealed class HttpRequest(IHttpContextInformation contextInfo, ushort maxUploads) : IHttpLifeCycle
+ internal sealed class HttpRequest(TransportManager transport, ushort maxUploads) : IHttpLifeCycle
#if DEBUG
,IStringSerializeable
#endif
@@ -48,7 +49,7 @@ namespace VNLib.Net.Http.Core
/// A transport stream wrapper that is positioned for reading
/// the entity body from the input stream
/// </summary>
- public readonly HttpInputStream InputStream = new(contextInfo);
+ public readonly HttpInputStream InputStream = new(transport);
/*
* Evil mutable structure that stores the http request state.
@@ -63,6 +64,7 @@ namespace VNLib.Net.Http.Core
*/
private HttpRequestState _state;
private readonly FileUpload[] _uploads = new FileUpload[maxUploads];
+ private readonly FileUpload[] _singleUpload = new FileUpload[1];
/// <summary>
/// Gets a mutable structure ref only used to initalize the request
@@ -120,13 +122,15 @@ namespace VNLib.Net.Http.Core
}
private void FreeUploadBuffers()
- {
- //Dispose all initialized files
+ {
+ //Dispose all initialized files, should be much faster than using Array.Clear();
for (int i = 0; i < _uploads.Length; i++)
{
_uploads[i].Free();
_uploads[i] = default;
}
+
+ _singleUpload[0] = default;
}
/// <summary>
@@ -136,22 +140,32 @@ namespace VNLib.Net.Http.Core
public bool CanAddUpload() => _state.UploadCount < _uploads.Length;
/// <summary>
- /// Attempts to add a file upload to the request if there
- /// is room for it. If there is no room, it will be ignored.
- /// See <see cref="CanAddUpload"/> to check if another upload can be added.
+ /// Attempts to obtain a reference to the next available
+ /// file upload in the request. If there are no more uploads
+ /// available, a null reference is returned.
/// </summary>
- /// <param name="upload">The file upload structure to add to the list</param>
- public void AddFileUpload(in FileUpload upload)
+ /// <returns>A reference within the upload array to add the file</returns>
+ public ref FileUpload AddFileUpload()
{
//See if there is room for another upload
if (CanAddUpload())
{
- //Add file to upload array and increment upload count
- _uploads[_state.UploadCount++] = upload;
+ //get ref to current position and increment the upload count
+ return ref _uploads[_state.UploadCount++];
}
+
+ return ref Unsafe.NullRef<FileUpload>();
}
/// <summary>
+ /// Attempts to add a file upload to the request if there
+ /// is room for it. If there is no room, it will be ignored.
+ /// See <see cref="CanAddUpload"/> to check if another upload can be added.
+ /// </summary>
+ /// <param name="upload">The file upload structure to add to the list</param>
+ public void AddFileUpload(in FileUpload upload) => AddFileUpload() = upload;
+
+ /// <summary>
/// Creates a new array and copies the uploads to it.
/// </summary>
/// <returns>The array clone of the file uploads</returns>
@@ -162,6 +176,13 @@ namespace VNLib.Net.Http.Core
return Array.Empty<FileUpload>();
}
+ //Shortcut for a single upload request (hotpath optimization)
+ if (_state.UploadCount == 1)
+ {
+ _singleUpload[0] = _uploads[0];
+ return _singleUpload;
+ }
+
//Create new array to hold uploads
FileUpload[] uploads = GC.AllocateUninitializedArray<FileUpload>(_state.UploadCount, false);
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
index 878622e..64e08c5 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -184,7 +184,7 @@ namespace VNLib.Net.Http.Core
//Default case is store as a file
default:
//add upload (if it fails thats fine, no memory to clean up)
- request.AddFileUpload(new(request.InputStream, false, request.State.ContentType, null));
+ request.AddFileUpload(new(request.InputStream, DisposeStream: false, request.State.ContentType, FileName: null));
break;
}
@@ -222,7 +222,7 @@ namespace VNLib.Net.Http.Core
Encoding encoding
)
{
- int length = 0;
+ int charsRead = 0;
do
{
//read async
@@ -238,18 +238,18 @@ namespace VNLib.Net.Http.Core
int numChars = encoding.GetCharCount(binBuffer.Span[..read]);
//Re-alloc buffer and guard for overflow
- charBuffer.ResizeIfSmaller(checked(numChars + length));
-
- //Decode and update position
- _ = encoding.GetChars(binBuffer.Span[..read], charBuffer.Span.Slice(length, numChars));
-
- //Update char count
- length += numChars;
+ charBuffer.ResizeIfSmaller(checked(numChars + charsRead));
+
+ _ = encoding.GetChars(
+ bytes: binBuffer.Span[..read],
+ chars: charBuffer.Span.Slice(charsRead, numChars)
+ );
+
+ charsRead += numChars;
} while (true);
-
- //Return the number of characters read
- return length;
+
+ return charsRead;
}
/*
@@ -299,8 +299,9 @@ namespace VNLib.Net.Http.Core
switch (headerType)
{
case HttpHelpers.ContentDisposition:
- //Parse the content dispostion
+
HttpHelpers.ParseDisposition(headerValue, out DispType, out Name, out FileName);
+
break;
case HttpRequestHeader.ContentType:
//The header value for content type should be an MIME content type
@@ -320,12 +321,13 @@ namespace VNLib.Net.Http.Core
//Only add the upload if the request can accept more uploads, otherwise drop it
if (state.Request.CanAddUpload())
{
- ReadOnlySpan<char> fileData = reader.Window.TrimCRLF();
-
- FileUpload upload = UploadFromString(fileData, state, FileName, ctHeaderVal);
-
- //Store the file in the uploads
- state.Request.AddFileUpload(in upload);
+ UploadFromString(
+ data: reader.Window.TrimCRLF(),
+ context: state,
+ filename: FileName,
+ contentType: ctHeaderVal,
+ upload: ref state.Request.AddFileUpload()
+ );
}
}
@@ -344,9 +346,16 @@ namespace VNLib.Net.Http.Core
/// <param name="data">The string data to copy</param>
/// <param name="context">The connection context</param>
/// <param name="filename">The name of the file</param>
- /// <param name="ct">The content type of the file data</param>
+ /// <param name="contentType">The content type of the file data</param>
+ /// <param name="upload">A reference to the file upload to assign</param>
/// <returns>The <see cref="FileUpload"/> container</returns>
- private static FileUpload UploadFromString(ReadOnlySpan<char> data, HttpContext context, string filename, ContentType ct)
+ private static void UploadFromString(
+ ReadOnlySpan<char> data,
+ HttpContext context,
+ string filename,
+ ContentType contentType,
+ ref FileUpload upload
+ )
{
IHttpContextInformation info = context;
IHttpMemoryPool pool = context.ParentServer.Config.MemoryPool;
@@ -365,7 +374,7 @@ namespace VNLib.Net.Http.Core
VnMemoryStream vms = VnMemoryStream.FromHandle(buffHandle, true, bytes, true);
//Create new upload wrapper that owns the stream
- return new(vms, true, ct, filename);
+ upload = new(vms, true, contentType, filename);
}
catch
{
diff --git a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
index cabb723..8e4e0e2 100644
--- a/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
+++ b/lib/Net.Http/src/Core/RequestParse/Http11ParseExtensions.cs
@@ -49,11 +49,26 @@ namespace VNLib.Net.Http.Core
/// </summary>
public ref struct Http1ParseState
{
- internal UriBuilder? Location;
- internal bool IsAbsoluteRequestUrl;
+ internal Uri? AbsoluteUri;
+ internal UriSegments Location;
internal long ContentLength;
}
+ /*
+ * Reduces load when parsing uri components
+ * and allows a one-time vaidation once the uri
+ * is compiled
+ */
+ internal ref struct UriSegments
+ {
+ public string Scheme;
+ public string Host;
+ public string Path;
+ public string Query;
+
+ public int Port;
+ }
+
/// <summary>
/// Reads the first line from the transport stream using the specified buffer
@@ -120,29 +135,23 @@ namespace VNLib.Net.Http.Core
return HttpStatusCode.HttpVersionNotSupported;
}
- //Set keepalive flag if http11
+ //Http 1.1 spec defaults to keepalive if the connection header is not set to close
reqState.KeepAlive = reqState.HttpVersion == HttpVersion.Http11;
- //Get the location segment from the request line
pathAndQuery = requestLine[(index + 1)..endloc].TrimCRLF();
//Process an absolute uri,
if (pathAndQuery.Contains("://", StringComparison.Ordinal))
{
//Convert the location string to a .net string and init the location builder (will perform validation when the Uri propery is used)
- parseState.Location = new(pathAndQuery.ToString());
- parseState.IsAbsoluteRequestUrl = true;
+ parseState.AbsoluteUri = new(pathAndQuery.ToString());
return 0;
}
- //Try to capture a realative uri
+ //Must be a relaive uri that starts with /
else if (pathAndQuery.Length > 0 && pathAndQuery[0] == '/')
{
- //Create a default location uribuilder
- parseState.Location = new()
- {
- //Set a default scheme
- Scheme = usingTls ? Uri.UriSchemeHttps : Uri.UriSchemeHttp,
- };
+ //Set default scheme
+ parseState.Location.Scheme = usingTls ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
//Need to manually parse the query string
int q = pathAndQuery.IndexOf('?');
@@ -161,6 +170,7 @@ namespace VNLib.Net.Http.Core
}
return 0;
}
+
//Cannot service an unknonw location
return HttpStatusCode.BadRequest;
}
@@ -334,16 +344,20 @@ namespace VNLib.Net.Http.Core
}
//Verify that the host matches the host header if absolue uri is set
- if (parseState.IsAbsoluteRequestUrl)
+ if (parseState.AbsoluteUri != null)
{
- if (!hostOnly.Equals(parseState.Location!.Host, StringComparison.OrdinalIgnoreCase))
+ if (!hostOnly.Equals(parseState.Location.Host, StringComparison.OrdinalIgnoreCase))
{
return HttpStatusCode.BadRequest;
}
}
- //store the host value
- parseState.Location!.Host = hostOnly;
+ /*
+ * Uri segments are only assigned/used if an absolute
+ * uri was not set in the request line.
+ */
+
+ parseState.Location.Host = hostOnly;
//If the port span is empty, no colon was found or the port is invalid
if (!port.IsEmpty)
@@ -444,16 +458,14 @@ namespace VNLib.Net.Http.Core
//Validate explicit range
if(!HttpRange.IsValidRangeValue(startRangeValue, endRangeValue))
{
- //Ignore and continue parsing headers
+ //If range is invalid were supposed to ignore it and continue
break;
}
-
- //Set full http range
+
reqState.Range = HttpRange.FullRange(startRangeValue, endRangeValue);
}
else
{
- //From-end range
reqState.Range = HttpRange.FromEnd(endRangeValue);
}
}
@@ -509,18 +521,43 @@ namespace VNLib.Net.Http.Core
return HttpStatusCode.BadRequest;
}
+ //Store absolute uri if set
+ if(parseState.AbsoluteUri != null)
+ {
+ reqState.Location = parseState.AbsoluteUri;
+ }
//Check the final location to make sure data was properly sent
- if (string.IsNullOrWhiteSpace(parseState.Location?.Host)
+ else if(string.IsNullOrWhiteSpace(parseState.Location.Host)
|| string.IsNullOrWhiteSpace(parseState.Location.Scheme)
|| string.IsNullOrWhiteSpace(parseState.Location.Path)
- )
+ )
{
return HttpStatusCode.BadRequest;
}
+ else
+ {
+ /*
+ * Double allocations are not ideal here, but for now, its the
+ * safest way to build and validate a foreign uri. Its better
+ * than it was.
+ *
+ * A string could be build from heap memory then passed to the
+ * uri constructor for validation, but this will work for now.
+ */
+
+ //Build the final uri if successfully parsed into segments
+ reqState.Location = new UriBuilder(
+ scheme: parseState.Location.Scheme,
+ host: parseState.Location.Host,
+ port: parseState.Location.Port,
+ path: parseState.Location.Path,
+ extraValue: null
+ )
+ {
+ Query = parseState.Location.Query,
+ }.Uri;
+ }
- //Store the finalized location
- reqState.Location = parseState.Location.Uri;
-
return 0;
}
diff --git a/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
index 0611095..a28ce09 100644
--- a/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
+++ b/lib/Net.Http/src/Core/Response/HeaderDataAccumulator.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Http
@@ -35,16 +35,10 @@ namespace VNLib.Net.Http.Core.Response
/// <summary>
/// Specialized data accumulator for compiling response headers
/// </summary>
- internal readonly struct HeaderDataAccumulator
+ internal readonly struct HeaderDataAccumulator(IResponseHeaderAccBuffer accBuffer, IHttpContextInformation ctx)
{
- private readonly IResponseHeaderAccBuffer _buffer;
- private readonly IHttpContextInformation _contextInfo;
-
- public HeaderDataAccumulator(IResponseHeaderAccBuffer accBuffer, IHttpContextInformation ctx)
- {
- _buffer = accBuffer;
- _contextInfo = ctx;
- }
+ private readonly IResponseHeaderAccBuffer _buffer = accBuffer;
+ private readonly IHttpContextInformation _contextInfo = ctx;
/// <summary>
/// Gets the accumulated response data as its memory buffer, and resets the internal accumulator
diff --git a/lib/Net.Http/src/Core/Response/HttpResponse.cs b/lib/Net.Http/src/Core/Response/HttpResponse.cs
index e354998..a038c9b 100644
--- a/lib/Net.Http/src/Core/Response/HttpResponse.cs
+++ b/lib/Net.Http/src/Core/Response/HttpResponse.cs
@@ -23,7 +23,6 @@
*/
using System;
-using System.IO;
using System.Net;
using System.Threading;
using System.Diagnostics;
@@ -39,7 +38,8 @@ using VNLib.Net.Http.Core.Buffering;
namespace VNLib.Net.Http.Core.Response
{
- internal sealed class HttpResponse(IHttpContextInformation ContextInfo, IHttpBufferManager manager) : IHttpLifeCycle
+
+ internal sealed class HttpResponse(IHttpContextInformation ContextInfo, TransportManager transport, IHttpBufferManager manager) : IHttpLifeCycle
#if DEBUG
, IStringSerializeable
#endif
@@ -47,8 +47,8 @@ namespace VNLib.Net.Http.Core.Response
const int DefaultCookieCapacity = 2;
private readonly Dictionary<string, HttpResponseCookie> Cookies = new(DefaultCookieCapacity, StringComparer.OrdinalIgnoreCase);
- private readonly DirectStream ReusableDirectStream = new();
- private readonly ChunkedStream ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, ContextInfo);
+ private readonly DirectStream ReusableDirectStream = new(transport);
+ private readonly ChunkedStream ReusableChunkedStream = new(manager.ChunkAccumulatorBuffer, transport, ContextInfo);
private readonly HeaderDataAccumulator Writer = new(manager.ResponseHeaderBuffer, ContextInfo);
private int _headerWriterPosition;
@@ -165,9 +165,6 @@ namespace VNLib.Net.Http.Core.Response
//Update sent headers
HeadersSent = true;
- //Get the transport stream to write the response data to
- Stream transport = ContextInfo.GetTransport();
-
/*
* ASYNC NOTICE: It is safe to get the memory block then return the task
* because the response writer is not cleaned up until the OnComplete()
@@ -175,7 +172,7 @@ namespace VNLib.Net.Http.Core.Response
*/
//Write the response data to the base stream
- return responseBlock.IsEmpty ? ValueTask.CompletedTask : transport.WriteAsync(responseBlock);
+ return responseBlock.IsEmpty ? ValueTask.CompletedTask : transport.Stream.WriteAsync(responseBlock);
}
/// <summary>
@@ -261,15 +258,14 @@ namespace VNLib.Net.Http.Core.Response
//reset after getting the written buffer
_headerWriterPosition = 0;
-
- //get base stream
- Stream bs = ContextInfo.GetTransport();
-
+
//Write the response data to the base stream
- await bs.WriteAsync(responseBlock);
+ await transport.Stream.WriteAsync(responseBlock);
- //Flush the base stream to send the data to the client
- await bs.FlushAsync();
+ /*
+ * Force flush should send data to client
+ */
+ await transport.FlushAsync();
}
/// <summary>
@@ -298,21 +294,11 @@ namespace VNLib.Net.Http.Core.Response
///<inheritdoc/>
public void OnRelease()
{
- ReusableChunkedStream.OnRelease();
- ReusableDirectStream.OnRelease();
Cookies.TrimExcess(DefaultCookieCapacity);
}
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void OnNewConnection(Stream transport)
- {
- ReusableChunkedStream.OnNewConnection(transport);
- ReusableDirectStream.OnNewConnection(transport);
- }
-
- ///<inheritdoc/>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void OnNewRequest()
{
//Default to okay status code
@@ -340,16 +326,16 @@ namespace VNLib.Net.Http.Core.Response
ReusableChunkedStream.OnComplete();
}
- private sealed class DirectStream : ReusableResponseStream, IDirectResponsWriter
+ private sealed class DirectStream(TransportManager transport) : IDirectResponsWriter
{
///<inheritdoc/>
- public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer) => transport!.WriteAsync(buffer);
+ public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer) => transport!.Stream.WriteAsync(buffer);
}
/// <summary>
/// Writes chunked HTTP message bodies to an underlying streamwriter
/// </summary>
- private sealed class ChunkedStream(IChunkAccumulatorBuffer buffer, IHttpContextInformation context) : ReusableResponseStream, IResponseDataWriter
+ private sealed class ChunkedStream(IChunkAccumulatorBuffer buffer, TransportManager transport, IHttpContextInformation context) : IResponseDataWriter
{
private readonly ChunkDataAccumulator _chunkAccumulator = new(buffer, context);
@@ -389,7 +375,7 @@ namespace VNLib.Net.Http.Core.Response
_accumulatedBytes = 0;
//Write remaining data to stream
- return transport!.WriteAsync(chunkData, CancellationToken.None);
+ return transport.Stream.WriteAsync(chunkData, CancellationToken.None);
}
#endregion
diff --git a/lib/Net.Http/src/Core/Response/ReusableResponseStream.cs b/lib/Net.Http/src/Core/Response/ReusableResponseStream.cs
deleted file mode 100644
index 3070c82..0000000
--- a/lib/Net.Http/src/Core/Response/ReusableResponseStream.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Net.Http
-* File: ReusableResponseStream.cs
-*
-* ReusableResponseStream.cs is part of VNLib.Net.Http which is part
-* of the larger VNLib collection of libraries and utilities.
-*
-* VNLib.Net.Http 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.Http 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.IO;
-
-namespace VNLib.Net.Http.Core.Response
-{
-
- internal abstract class ReusableResponseStream
- {
- protected Stream? transport;
-
- /// <summary>
- /// Called when a new connection is established
- /// </summary>
- /// <param name="transport"></param>
- public virtual void OnNewConnection(Stream transport) => this.transport = transport;
-
- /// <summary>
- /// Called when the connection is released
- /// </summary>
- public virtual void OnRelease() => transport = null;
-
- }
-} \ No newline at end of file
diff --git a/lib/Net.Http/src/Core/Response/TransportManager.cs b/lib/Net.Http/src/Core/Response/TransportManager.cs
new file mode 100644
index 0000000..45efc4b
--- /dev/null
+++ b/lib/Net.Http/src/Core/Response/TransportManager.cs
@@ -0,0 +1,99 @@
+/*
+* Copyright (c) 2024 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Net.Http
+* File: TransportManager.cs
+*
+* TransportManager.cs is part of VNLib.Net.Http which is part of
+* the larger VNLib collection of libraries and utilities.
+*
+* VNLib.Net.Http 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.Http 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.IO;
+using System.Buffers;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace VNLib.Net.Http.Core.Response
+{
+ internal sealed class TransportManager
+ {
+ public bool IsBufferWriter;
+
+#nullable disable
+
+#if DEBUG
+
+ private Stream _stream;
+ private IBufferWriter<byte> _asWriter;
+
+ public Stream Stream
+ {
+ get
+ {
+ Debug.Assert(_stream != null, "Transport stream was accessed but was set to null");
+ return _stream;
+ }
+ set => _stream = value;
+ }
+
+ public IBufferWriter<byte> Writer
+ {
+ get
+ {
+ Debug.Assert(_asWriter != null, "Transport buffer writer accessed but the writer is null");
+ return _asWriter;
+ }
+ set => _asWriter = value;
+ }
+
+#else
+ public Stream Stream;
+ public IBufferWriter<byte> Writer;
+#endif
+
+#nullable restore
+
+ public Task FlushAsync() => Stream.FlushAsync();
+
+ /// <summary>
+ /// Assigns a new transport stream to the wrapper
+ /// as a new connection is assigned to write responses to
+ /// </summary>
+ /// <param name="transportStream">The transport stream to wrap</param>
+ public void OnNewConnection(Stream transportStream)
+ {
+ Stream = transportStream;
+
+ //Capture a buffer writer if the incoming stream supports direct writing
+ if (transportStream is IBufferWriter<byte> bw)
+ {
+ Writer = bw;
+ IsBufferWriter = true;
+ }
+ }
+
+ /// <summary>
+ /// Closes the current connection and resets the transport stream
+ /// </summary>
+ public void OnRelease()
+ {
+ Stream = null;
+ Writer = null;
+ IsBufferWriter = false;
+ }
+ }
+} \ No newline at end of file
diff --git a/lib/Net.Transport.SimpleTCP/src/ITransportInterface.cs b/lib/Net.Transport.SimpleTCP/src/ITransportInterface.cs
index 5d16c69..b5afef1 100644
--- a/lib/Net.Transport.SimpleTCP/src/ITransportInterface.cs
+++ b/lib/Net.Transport.SimpleTCP/src/ITransportInterface.cs
@@ -24,6 +24,7 @@
using System;
+using System.Buffers;
using System.Threading;
using System.Threading.Tasks;
@@ -68,5 +69,17 @@ namespace VNLib.Net.Transport.Tcp
/// <returns>The number of bytes received</returns>
int Recv(Span<byte> buffer, int timeout);
+ /// <summary>
+ /// Gets as transport buffer writer for more effecient writes
+ /// </summary>
+ IBufferWriter<byte> SendBuffer { get; }
+
+ /// <summary>
+ /// Flushes the send buffer
+ /// </summary>
+ /// <param name="timeout"></param>
+ /// <param name="cancellation"></param>
+ /// <returns>A task that completes when pending write data has been sent</returns>
+ ValueTask FlushSendAsync(int timeout, CancellationToken cancellation);
}
} \ No newline at end of file
diff --git a/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs b/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
index 00b5d7c..1867748 100644
--- a/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
+++ b/lib/Net.Transport.SimpleTCP/src/ReusableNetworkStream.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2023 Vaughn Nugent
+* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Net.Transport.SimpleTCP
@@ -33,6 +33,7 @@
using System;
+using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -45,7 +46,7 @@ namespace VNLib.Net.Transport.Tcp
/// <summary>
/// A reusable stream that marshals data between the socket pipeline and the application
/// </summary>
- internal sealed class ReusableNetworkStream : Stream
+ internal sealed class ReusableNetworkStream : Stream, IBufferWriter<byte>
{
#region stream basics
public override bool CanRead => true;
@@ -83,18 +84,31 @@ namespace VNLib.Net.Transport.Tcp
//Timer used to cancel pipeline recv timeouts
private readonly ITransportInterface Transport;
-
- internal ReusableNetworkStream(ITransportInterface transport)
- {
- Transport = transport;
- }
+
+ internal ReusableNetworkStream(ITransportInterface transport) => Transport = transport;
///<inheritdoc/>
public override void Close()
{ }
///<inheritdoc/>
- public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+ public override Task FlushAsync(CancellationToken cancellationToken)
+ => Transport.FlushSendAsync(_sendTimeoutMs, cancellationToken).AsTask();
+
+
+ /*
+ * Expose the buffer writer interface on the stream
+ * for more efficient publishing
+ */
+
+ ///<inheritdoc/>
+ public void Advance(int count) => Transport.SendBuffer.Advance(count);
+
+ ///<inheritdoc/>
+ public Memory<byte> GetMemory(int sizeHint = 0) => Transport.SendBuffer.GetMemory(sizeHint);
+
+ ///<inheritdoc/>
+ public Span<byte> GetSpan(int sizeHint = 0) => Transport.SendBuffer.GetSpan(sizeHint);
///<inheritdoc/>
public override void Flush()
@@ -136,5 +150,6 @@ namespace VNLib.Net.Transport.Tcp
*/
public override ValueTask DisposeAsync() => ValueTask.CompletedTask;
+
}
-} \ No newline at end of file
+}
diff --git a/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs b/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs
index e47dd37..27d1e27 100644
--- a/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs
+++ b/lib/Net.Transport.SimpleTCP/src/SocketPipeLineWorker.cs
@@ -234,6 +234,7 @@ namespace VNLib.Net.Transport.Tcp
private FlushResult _recvFlushRes;
+
public async Task RecvDoWorkAsync<TIO>(TIO sock, int bytesTransferred, int recvBufferSize)
where TIO : ISocketIo
@@ -335,6 +336,28 @@ namespace VNLib.Net.Transport.Tcp
await RecvPipe.Reader.CompleteAsync();
}
+ ///<inheritdoc/>
+ public IBufferWriter<byte> SendBuffer => SendPipe.Writer;
+
+ ///<inheritdoc/>
+ public ValueTask FlushSendAsync(int timeout, CancellationToken cancellation)
+ {
+ //See if timer is required
+ if (timeout < 1)
+ {
+ NoOpTimerWrapper noOpTimer = default;
+
+ //no timer
+ return SendWithTimerInternalAsync(in noOpTimer, cancellation);
+ }
+ else
+ {
+ TpTimerWrapper sendTimer = new(SendTimer, timeout);
+
+ //Pass new send timer to send method
+ return SendWithTimerInternalAsync(in sendTimer, cancellation);
+ }
+ }
private static async Task AwaitFlushTask<TTimer>(ValueTask<FlushResult> valueTask, TTimer timer)
where TTimer : INetTimer
@@ -350,6 +373,14 @@ namespace VNLib.Net.Transport.Tcp
}
}
+ private ValueTask SendAsync(ReadOnlySpan<byte> data, int timeout, CancellationToken cancellation)
+ {
+ //Publish send data to send pipe
+ CopyAndPublishDataOnSendPipe(data, _sysSocketBufferSize, SendPipe.Writer);
+
+ return FlushSendAsync(timeout, cancellation);
+ }
+
private ValueTask SendWithTimerInternalAsync<TTimer>(in TTimer timer, CancellationToken cancellation)
where TTimer : INetTimer
{
@@ -388,28 +419,6 @@ namespace VNLib.Net.Transport.Tcp
}
}
- private ValueTask SendAsync(ReadOnlySpan<byte> data, int timeout, CancellationToken cancellation)
- {
- //Publish send data to send pipe
- CopyAndPublishDataOnSendPipe(data, _sysSocketBufferSize, SendPipe.Writer);
-
- //See if timer is required
- if (timeout < 1)
- {
- NoOpTimerWrapper noOpTimer = default;
-
- //no timer
- return SendWithTimerInternalAsync(in noOpTimer, cancellation);
- }
- else
- {
- TpTimerWrapper sendTimer = new(SendTimer, timeout);
-
- //Pass new send timer to send method
- return SendWithTimerInternalAsync(in sendTimer, cancellation);
- }
- }
-
ValueTask ITransportInterface.SendAsync(ReadOnlyMemory<byte> data, int timeout, CancellationToken cancellation)
{
return SendAsync(data.Span, timeout, cancellation);
@@ -542,7 +551,6 @@ namespace VNLib.Net.Transport.Tcp
RecvTimer.Stop();
}
}
-
private static class ThrowHelpers
{
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs
index c433527..99d4cf1 100644
--- a/lib/Utils/src/Extensions/MemoryExtensions.cs
+++ b/lib/Utils/src/Extensions/MemoryExtensions.cs
@@ -48,19 +48,8 @@ namespace VNLib.Utils.Extensions
/// <param name="size">The minimum size array to allocate</param>
/// <param name="zero">Should elements from 0 to size be set to default(T)</param>
/// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
- public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged
- {
- ArgumentNullException.ThrowIfNull(pool);
-
- T[] array = pool.Rent(size);
-
- if (zero)
- {
- MemoryUtil.InitializeBlock(array, (uint)size);
- }
-
- return new(pool, array, size);
- }
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged
+ => MemoryUtil.UnsafeAlloc<T>(pool, size, zero);
/// <summary>
/// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
@@ -72,19 +61,7 @@ namespace VNLib.Utils.Extensions
/// <param name="zero">Should elements from 0 to size be set to default(T)</param>
/// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
public static IMemoryHandle<T> SafeAlloc<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : struct
- {
- ArgumentNullException.ThrowIfNull(pool);
-
- T[] array = pool.Rent(size);
-
- if (zero)
- {
- MemoryUtil.InitializeBlock(array, (uint)size);
- }
-
- //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed
- return new ArrayPoolBuffer<T>(pool, array, size);
- }
+ => MemoryUtil.SafeAlloc<T>(pool, size, zero);
/// <summary>
/// Retreives a buffer that is at least the reqested length, and clears the array from 0-size.
@@ -111,13 +88,6 @@ namespace VNLib.Utils.Extensions
}
/// <summary>
- /// Copies the characters within the memory handle to a <see cref="string"/>
- /// </summary>
- /// <returns>The string representation of the buffer</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string ToString<T>(this T charBuffer) where T : IMemoryHandle<char> => charBuffer.Span.ToString();
-
- /// <summary>
/// Wraps the <see cref="IMemoryHandle{T}"/> instance in System.Buffers.MemoryManager
/// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles.
/// </summary>
@@ -131,7 +101,8 @@ namespace VNLib.Utils.Extensions
/// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle) => new SysBufferMemoryManager<T>(handle, ownsHandle);
+ public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle)
+ => new SysBufferMemoryManager<T>(handle, ownsHandle);
/// <summary>
/// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance
@@ -168,7 +139,8 @@ namespace VNLib.Utils.Extensions
/// </returns>
/// <exception cref="OverflowException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetIntLength<T>(this IMemoryHandle<T> handle) => Convert.ToInt32(handle.Length);
+ public static int GetIntLength<T>(this IMemoryHandle<T> handle)
+ => Convert.ToInt32(handle.Length);
/// <summary>
/// Gets the integer length (number of elements) of the <see cref="UnsafeMemoryHandle{T}"/>
@@ -181,7 +153,8 @@ namespace VNLib.Utils.Extensions
/// </returns>
//Method only exists for consistancy since unsafe handles are always 32bit
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged => handle.IntLength;
+ public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged
+ => handle.IntLength;
/// <summary>
/// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks
@@ -440,16 +413,8 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ArgumentException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
- {
- ArgumentNullException.ThrowIfNull(heap);
- //Minimum of one element
- elements = Math.Max(elements, 1);
- //If zero flag is set then specify zeroing memory
- IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero);
- //Return handle wrapper
- return new MemoryHandle<T>(heap, block, elements, zero);
- }
+ public static unsafe MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
+ => MemoryUtil.SafeAlloc<T>(heap, elements, zero);
/// <summary>
/// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
@@ -464,10 +429,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle<T> Alloc<T>(this IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged
- {
- ArgumentOutOfRangeException.ThrowIfNegative(elements);
- return Alloc<T>(heap, (nuint)elements, zero);
- }
+ => MemoryUtil.SafeAlloc<T>(heap, elements, zero);
/// <summary>
/// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer
@@ -481,10 +443,8 @@ namespace VNLib.Utils.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T : unmanaged
{
- //Aloc block
- MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
-
- //Copy initial data
+ MemoryHandle<T> handle = Alloc<T>(heap, initialData.Length);
+
MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length);
return handle;
@@ -502,10 +462,8 @@ namespace VNLib.Utils.Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlyMemory<T> initialData) where T : unmanaged
{
- //Aloc block
- MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
-
- //Copy initial data
+ MemoryHandle<T> handle = Alloc<T>(heap, initialData.Length);
+
MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length);
return handle;
@@ -542,25 +500,8 @@ namespace VNLib.Utils.Extensions
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
- {
- ArgumentNullException.ThrowIfNull(heap);
-
- if (elements < 1)
- {
- //Return an empty handle
- return new UnsafeMemoryHandle<T>();
- }
-
- //Get element size
- nuint elementSize = (nuint)Unsafe.SizeOf<T>();
-
- //If zero flag is set then specify zeroing memory (safe case because of the above check)
- IntPtr block = heap.Alloc((nuint)elements, elementSize, zero);
-
- //handle wrapper
- return new (heap, block, elements);
- }
+ public unsafe static UnsafeMemoryHandle<T> UnsafeAlloc<T>(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ => MemoryUtil.UnsafeAlloc<T>(heap, elements, zero);
#region VnBufferWriter
@@ -775,13 +716,6 @@ namespace VNLib.Utils.Extensions
}
/// <summary>
- /// Converts the buffer data to a <see cref="PrivateString"/>
- /// </summary>
- /// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true);
-
- /// <summary>
/// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer
/// </summary>
/// <returns>A <see cref="Span{T}"/> over the modified data</returns>
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
index 2e5ec40..23df869 100644
--- a/lib/Utils/src/IO/VnMemoryStream.cs
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -41,6 +41,8 @@ namespace VNLib.Utils.IO
/// </summary>
public sealed class VnMemoryStream : Stream, ICloneable
{
+ public const int DefaultBufferSize = 4096;
+
private nint _position;
private nint _length;
private bool _isReadonly;
@@ -108,7 +110,7 @@ namespace VNLib.Utils.IO
/// <param name="heap"><see cref="Win32PrivateHeap"/> to allocate memory from</param>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ArgumentNullException"></exception>
- public VnMemoryStream(IUnmangedHeap heap) : this(heap, 0, false) { }
+ public VnMemoryStream(IUnmangedHeap heap) : this(heap, DefaultBufferSize, false) { }
/// <summary>
/// Creates a new memory stream and pre-allocates the internal
diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
index e2e7434..6e4f9b0 100644
--- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs
+++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
@@ -27,14 +27,83 @@ using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
-using VNLib.Utils.Extensions;
-
namespace VNLib.Utils.Memory
{
public static unsafe partial class MemoryUtil
{
#region alloc
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool UseUnmanagedHeap<T>(IUnmangedHeap heap, nuint elements)
+ {
+ /*
+ * We may allocate from the share heap only if the heap is not using locks
+ * or if the element size could cause performance issues because its too large
+ * to use a managed array.
+ *
+ * We want to avoid allocations, that may end up in the LOH if we can
+ */
+
+ return (heap.CreationFlags & HeapCreation.UseSynchronization) == 0
+ || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE;
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentNullException.ThrowIfNull(heap);
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+
+ if (elements == 0)
+ {
+ //Return an empty handle
+ return default;
+ }
+
+ //If zero flag is set then specify zeroing memory (safe case because of the above check)
+ IntPtr block = heap.Alloc((nuint)elements, (nuint)sizeof(T), zero);
+
+ return new(heap, block, elements);
+ }
+
+ /// <summary>
+ /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
+ /// array when work is completed
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="pool"></param>
+ /// <param name="size">The minimum size array to allocate</param>
+ /// <param name="zero">Should elements from 0 to size be set to default(T)</param>
+ /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
+ public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged
+ {
+ ArgumentNullException.ThrowIfNull(pool);
+
+ if (size <= 0)
+ {
+ return default;
+ }
+
+ T[] array = pool.Rent(size);
+
+ if (zero)
+ {
+ InitializeBlock(array, (uint)size);
+ }
+
+ return new(pool, array, size);
+ }
+
/// <summary>
/// Allocates a block of unmanaged, or pooled manaaged memory depending on
/// compilation flags and runtime unamanged allocators.
@@ -43,40 +112,20 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
if (elements == 0)
{
return default;
}
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
-
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE)
- {
- // Alloc from heap
- IntPtr block = Shared.Alloc((uint)elements, (uint)sizeof(T), zero);
- //Init new handle
- return new(Shared, block, elements);
- }
- else
- {
- //Rent the array from the pool
- return ArrayPool<T>.Shared.UnsafeAlloc(elements, zero);
- }
+ return UseUnmanagedHeap<T>(Shared, (uint)elements)
+ ? UnsafeAlloc<T>(Shared, elements, zero)
+ : UnsafeAlloc(ArrayPool<T>.Shared, elements, zero);
}
/// <summary>
@@ -88,18 +137,81 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return UnsafeAlloc<T>(elements: (int)NearestPage<T>(elements), zero);
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static MemoryHandle<T> SafeAlloc<T>(IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentNullException.ThrowIfNull(heap);
+
+ //Return empty handle if no elements were specified
+ if (elements == 0)
{
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
+ return new MemoryHandle<T>();
}
+
+ IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero);
- //Round to nearest page (in bytes)
- nint np = NearestPage<T>(elements);
- return UnsafeAlloc<T>((int)np, zero);
+ return new MemoryHandle<T>(heap, block, elements, zero);
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
+ /// </summary>
+ /// <typeparam name="T">Unmanaged data type to create a block of</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="elements">The size of the block (number of elements)</param>
+ /// <param name="zero">A flag that zeros the allocated block before returned</param>
+ /// <returns>The unmanaged <see cref="MemoryHandle{T}"/></returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> SafeAlloc<T>(IUnmangedHeap heap, nint elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAlloc<T>(heap, (nuint)elements, zero);
+ }
+
+ /// <summary>
+ /// Rents a new array and stores it as a resource within an <see cref="OpenResourceHandle{T}"/> to return the
+ /// array when work is completed
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="pool"></param>
+ /// <param name="size">The minimum size array to allocate</param>
+ /// <param name="zero">Should elements from 0 to size be set to default(T)</param>
+ /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns>
+ public static ArrayPoolBuffer<T> SafeAlloc<T>(ArrayPool<T> pool, int size, bool zero = false) where T : struct
+ {
+ ArgumentNullException.ThrowIfNull(pool);
+
+ T[] array = pool.Rent(size);
+
+ if (zero)
+ {
+ InitializeBlock(array, (uint)size);
+ }
+
+ //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed
+ return new ArrayPoolBuffer<T>(pool, array, size);
}
/// <summary>
@@ -110,35 +222,60 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
- public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IMemoryHandle<T> SafeAlloc<T>(nuint elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
-
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE)
+ if (UseUnmanagedHeap<T>(Shared, elements))
{
- return Shared.Alloc<T>(elements, zero);
+ return SafeAlloc<T>(Shared, elements, zero);
}
else
{
- return new ArrayPoolBuffer<T>(ArrayPool<T>.Shared, elements, zero);
+ //Should never happen because max pool size guards against this
+ Debug.Assert(elements <= int.MaxValue);
+
+ return SafeAlloc(ArrayPool<T>.Shared, (int)elements, zero);
}
}
/// <summary>
/// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAlloc<T>((nuint)elements, zero);
+ }
+
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators, rounded up to the
+ /// neareset memory page.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static IMemoryHandle<T> SafeAllocNearestPage<T>(nuint elements, bool zero = false) where T : unmanaged
+ => SafeAlloc<T>(elements: NearestPage<T>(elements), zero);
+
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
/// compilation flags and runtime unamanged allocators, rounded up to the
/// neareset memory page.
/// </summary>
@@ -146,21 +283,52 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMemoryHandle<T> SafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAllocNearestPage<T>((nuint)elements, zero);
+ }
- //Round to nearest page (in bytes)
- nint np = NearestPage<T>(elements);
- return SafeAlloc<T>((int)np, zero);
+ /// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators, rounded up to the
+ /// neareset memory page.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <param name="heap">The heap to allocate the block of memory from</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> SafeAllocNearestPage<T>(IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+
+ return SafeAllocNearestPage<T>(heap, (nuint)elements, zero);
}
/// <summary>
+ /// Allocates a block of unmanaged, or pooled manaaged memory depending on
+ /// compilation flags and runtime unamanged allocators, rounded up to the
+ /// neareset memory page.
+ /// </summary>
+ /// <typeparam name="T">The unamanged type to allocate</typeparam>
+ /// <param name="elements">The number of elements of the type within the block</param>
+ /// <param name="zero">Flag to zero elements during allocation before the method returns</param>
+ /// <param name="heap">The heap to allocate the block of memory from</param>
+ /// <returns>A handle to the block of memory</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="OutOfMemoryException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryHandle<T> SafeAllocNearestPage<T>(IUnmangedHeap heap, nuint elements, bool zero = false) where T : unmanaged
+ => SafeAlloc<T>(heap, elements: NearestPage<T>(elements), zero);
+
+ /// <summary>
/// Allocates a structure of the specified type on the specified
/// unmanged heap and optionally zero's it's memory
/// </summary>
@@ -191,13 +359,8 @@ namespace VNLib.Utils.Memory
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged
- {
- //Alloc structure
- T* ptr = StructAlloc<T>(heap, zero);
- //Get a reference and assign it
- return ref Unsafe.AsRef<T>(ptr);
- }
+ public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged
+ => ref Unsafe.AsRef<T>(StructAlloc<T>(heap, zero));
/// <summary>
/// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/>
@@ -207,7 +370,8 @@ namespace VNLib.Utils.Memory
/// <param name="structPtr">A pointer to the unmanaged structure to free</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged => StructFree(heap, (void*)structPtr);
+ public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged
+ => StructFree(heap, (void*)structPtr);
/// <summary>
/// Frees a structure allocated with <see cref="StructAllocRef{T}(IUnmangedHeap, bool)"/>
@@ -218,7 +382,8 @@ namespace VNLib.Utils.Memory
/// <param name="structRef">A reference to the unmanaged structure to free</param>
/// <exception cref="ArgumentNullException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged => StructFree(heap, Unsafe.AsPointer(ref structRef));
+ public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged
+ => StructFree(heap, Unsafe.AsPointer(ref structRef));
/// <summary>
/// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/>
@@ -233,7 +398,7 @@ namespace VNLib.Utils.Memory
//Get intpointer
IntPtr ptr = (IntPtr)structPtr;
- //Free
+
bool isFree = heap.Free(ref ptr);
Debug.Assert(isFree, $"Structure free failed for heap {heap.GetHashCode()}, struct address {ptr:x}");
}
@@ -249,39 +414,20 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static UnsafeMemoryHandle<byte> UnsafeAlloc(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
if(elements == 0)
{
return default;
}
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
-
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE)
- {
- // Alloc from heap
- IntPtr block = Shared.Alloc((uint)elements, 1, zero);
- //Init new handle
- return new(Shared, block, elements);
- }
- else
- {
- return ArrayPool<byte>.Shared.UnsafeAlloc(elements, zero);
- }
+ return UseUnmanagedHeap<byte>(Shared, (uint)elements)
+ ? UnsafeAlloc<byte>(Shared, elements, zero)
+ : UnsafeAlloc(ArrayPool<byte>.Shared, elements, zero);
}
/// <summary>
@@ -292,19 +438,15 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static UnsafeMemoryHandle<byte> UnsafeAllocNearestPage(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
//Round to nearest page (in bytes)
- nint np = NearestPage(elements);
- return UnsafeAlloc((int)np, zero);
+ return UnsafeAlloc(elements: (int)NearestPage(elements), zero);
}
/// <summary>
@@ -314,31 +456,15 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static IMemoryHandle<byte> SafeAlloc(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
-
- /*
- * We may allocate from the share heap only if the heap is not using locks
- * or if the element size could cause performance issues because its too large
- * to use a managed array.
- *
- * We want to avoid allocations, that may end up in the LOH if we can
- */
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
- if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE)
- {
- return Shared.Alloc<byte>(elements, zero);
- }
- else
- {
- return new ArrayPoolBuffer<byte>(ArrayPool<byte>.Shared, elements, zero);
- }
+ return UseUnmanagedHeap<byte>(Shared, (uint)elements)
+ ? SafeAlloc<byte>(Shared, (nuint)elements, zero)
+ : SafeAlloc(ArrayPool<byte>.Shared, elements, zero);
}
/// <summary>
@@ -349,18 +475,12 @@ namespace VNLib.Utils.Memory
/// <param name="elements">The number of elements of the type within the block</param>
/// <param name="zero">Flag to zero elements during allocation before the method returns</param>
/// <returns>A handle to the block of memory</returns>
- /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OutOfMemoryException"></exception>
public static IMemoryHandle<byte> SafeAllocNearestPage(int elements, bool zero = false)
{
- if (elements < 0)
- {
- throw new ArgumentException("Number of elements must be a positive integer", nameof(elements));
- }
-
- //Round to nearest page (in bytes)
- nint np = NearestPage(elements);
- return SafeAlloc((int)np, zero);
+ ArgumentOutOfRangeException.ThrowIfNegative(elements);
+ return SafeAlloc(elements: (int)NearestPage(elements), zero);
}
#endregion
diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs
index 7f42761..a9730b7 100644
--- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs
+++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs
@@ -23,6 +23,7 @@
*/
using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
@@ -111,25 +112,32 @@ namespace VNLib.Utils.Memory
bool result;
//If disposed, set the block handle to zero and exit to avoid raising exceptions during finalization
- if (IsClosed || IsInvalid)
+ if (IsClosed)
{
block = LPVOID.Zero;
return true;
}
+ /*
+ * Checking for invalid is not really necesasry because
+ * the only way the handle can be invalidated is
+ * if some derrived class mutates the handle value
+ * and doesn't close the handle
+ */
+ Debug.Assert(IsInvalid == false);
+
if ((flags & HeapCreation.UseSynchronization) > 0)
{
//wait for lock
lock (HeapLock)
- {
- //Free block
+ {
result = FreeBlock(block);
//Release lock before releasing handle
}
}
else
{
- //No lock
+ //No lock needed
result = FreeBlock(block);
}
diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs
index 63342b5..68bc35c 100644
--- a/lib/Utils/tests/Memory/MemoryUtilTests.cs
+++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs
@@ -202,7 +202,7 @@ namespace VNLib.Utils.Memory.Tests
{ }
//test against negative number
- Assert.ThrowsException<ArgumentException>(() => MemoryUtil.UnsafeAlloc<byte>(-1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.UnsafeAlloc<byte>(-1));
//Alloc large block test (100mb)
const int largTestSize = 100000 * 1024;
@@ -257,7 +257,7 @@ namespace VNLib.Utils.Memory.Tests
}
//Negative value
- Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.UnsafeAlloc<byte>(-1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = MemoryUtil.UnsafeAlloc<byte>(-1));
/*
@@ -315,7 +315,7 @@ namespace VNLib.Utils.Memory.Tests
}
//Negative value
- Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.SafeAlloc<byte>(-1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = MemoryUtil.SafeAlloc<byte>(-1));
/*
* Alloc random sized blocks in a loop, confirm they are empty