From 92e182ceaf843f8d859d38faa8b2c0ff53207ff6 Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 21 Jun 2024 16:02:34 -0400 Subject: feat: Multi transport listeners --- lib/Net.Http/src/Core/HttpServerBase.cs | 48 +++++++++++++++------- lib/Plugins.Essentials/src/Sessions/SessionInfo.cs | 36 ++-------------- lib/Utils/src/Memory/MemoryHandle.cs | 8 ++-- lib/Utils/src/Memory/MemoryUtil.cs | 3 +- lib/Utils/src/Memory/MemoryUtilAlloc.cs | 10 ++--- lib/Utils/src/Memory/UnsafeMemoryHandle.cs | 45 ++++++++++++-------- 6 files changed, 76 insertions(+), 74 deletions(-) diff --git a/lib/Net.Http/src/Core/HttpServerBase.cs b/lib/Net.Http/src/Core/HttpServerBase.cs index ec6e73d..5954057 100644 --- a/lib/Net.Http/src/Core/HttpServerBase.cs +++ b/lib/Net.Http/src/Core/HttpServerBase.cs @@ -36,6 +36,7 @@ */ using System; +using System.Linq; using System.Threading; using System.Net.Sockets; using System.Threading.Tasks; @@ -74,7 +75,7 @@ namespace VNLib.Net.Http /// internal static readonly Memory WriteOnlyScratchBuffer = new byte[64 * 1024]; - private readonly ITransportProvider Transport; + private readonly ITransportProvider[] Transports; private readonly FrozenDictionary ServerRoots; private readonly IWebRoot? _wildcardRoot; private readonly HttpConfig _config; @@ -114,10 +115,10 @@ namespace VNLib.Net.Http /// Immutable data structures are initialzed. /// /// The configuration used to create the instance - /// The transport provider to listen to connections from + /// An enumeration of transports to listen for connections on /// A collection of s that route incomming connetctions /// - public HttpServer(HttpConfig config, ITransportProvider transport, IEnumerable sites) + public HttpServer(HttpConfig config, IEnumerable transports, IEnumerable sites) { //Validate the configuration ValidateConfig(in config); @@ -127,12 +128,13 @@ namespace VNLib.Net.Http ServerRoots = sites.ToFrozenDictionary(static r => r.Hostname, static tv => tv, StringComparer.OrdinalIgnoreCase); //Compile and store the timeout keepalive header KeepAliveTimeoutHeaderValue = $"timeout={(int)_config.ConnectionKeepAlive.TotalSeconds}"; - //Setup config copy with the internal http pool - Transport = transport; + + Transports = transports.ToArray(); + //Cache supported compression methods, or none if compressor is null - SupportedCompressionMethods = config.CompressorManager == null ? - CompressionMethod.None : - config.CompressorManager.GetSupportedMethods(); + SupportedCompressionMethods = config.CompressorManager == null + ? CompressionMethod.None + : config.CompressorManager.GetSupportedMethods(); //Create a new context store ContextStore = ObjectRental.CreateReusable(() => new HttpContext(this, SupportedCompressionMethods)); @@ -242,16 +244,34 @@ namespace VNLib.Net.Http public Task Start(CancellationToken cancellationToken) { StopToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - //Start servers with the new token source - Transport.Start(StopToken.Token); - //Start the listen task - return Task.Run(ListenWorkerDoWork, cancellationToken); + + //Start servers with the new token source before listening for connections + Array.ForEach(Transports, p => p.Start(StopToken.Token)); + + //Listen to connections on all transports async + IEnumerable runTasks = Transports.Select(ListenAsync); + + //Set running flag and will be reset when all listening tasks are done + Running = true; + + return Task.WhenAll(runTasks) + .ContinueWith( + OnAllStopped, + CancellationToken.None, + TaskContinuationOptions.RunContinuationsAsynchronously, + TaskScheduler.Default + ); + + //Defer listening tasks to the task scheduler to avoid blocking this thread + Task ListenAsync(ITransportProvider tp) => Task.Run(() => ListenWorkerDoWork(tp), cancellationToken); + + void OnAllStopped(Task _) => Running = false; } /* * A worker task that listens for connections from the transport */ - private async Task ListenWorkerDoWork() + private async Task ListenWorkerDoWork(ITransportProvider transport) { //Set running flag Running = true; @@ -264,7 +284,7 @@ namespace VNLib.Net.Http try { //Listen for new connection - ITransportContext ctx = await Transport.AcceptAsync(StopToken!.Token); + ITransportContext ctx = await transport.AcceptAsync(StopToken!.Token); //Try to dispatch the received event _ = DataReceivedAsync(ctx).ConfigureAwait(false); diff --git a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs index 2edb30c..eccfdfc 100644 --- a/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs +++ b/lib/Plugins.Essentials/src/Sessions/SessionInfo.cs @@ -65,9 +65,7 @@ namespace VNLib.Plugins.Essentials.Sessions { None = 0x00, IsSet = 0x01, - IpMatch = 0x02, - IsCrossOrigin = 0x04, - CrossOriginMatch = 0x08, + IpMatch = 0x02 } private readonly ISession UserSession; @@ -116,24 +114,6 @@ namespace VNLib.Plugins.Essentials.Sessions get => _flags.HasFlag(SessionFlags.IpMatch); } - /// - /// If the current connection and stored session have matching cross origin domains - /// - public readonly bool CrossOriginMatch - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _flags.HasFlag(SessionFlags.CrossOriginMatch); - } - - /// - /// Was the original session cross origin? - /// - public readonly bool CrossOrigin - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _flags.HasFlag(SessionFlags.IsCrossOrigin); - } - /// /// Was this session just created on this connection? /// @@ -252,10 +232,10 @@ namespace VNLib.Plugins.Essentials.Sessions { UserSession = session; - SessionFlags flags = SessionFlags.IsSet; + _flags |= SessionFlags.IsSet; //Set ip match flag if current ip and stored ip match - flags |= trueIp.Equals(session.UserIP) ? SessionFlags.IpMatch : SessionFlags.None; + _flags |= trueIp.Equals(session.UserIP) ? SessionFlags.IpMatch : SessionFlags.None; //If the session is new, we can store intial security variables if (session.IsNew) @@ -266,8 +246,6 @@ namespace VNLib.Plugins.Essentials.Sessions UserAgent = ci.UserAgent; SpecifiedOrigin = ci.Origin; SecurityProcol = ci.GetSslProtocol(); - - flags |= ci.CrossOrigin ? SessionFlags.IsCrossOrigin : SessionFlags.None; } else { @@ -275,15 +253,7 @@ namespace VNLib.Plugins.Essentials.Sessions UserAgent = session.GetUserAgent(); SpecifiedOrigin = session.GetOriginUri(); SecurityProcol = session.GetSecurityProtocol(); - - flags |= session.IsCrossOrigin() ? SessionFlags.IsCrossOrigin : SessionFlags.None; } - - //Set cross origin orign match flags, if the stored origin, and connection origin - flags |= ci.Origin != null && ci.Origin.Equals(SpecifiedOrigin) ? SessionFlags.CrossOriginMatch : SessionFlags.None; - - //store flags - _flags = flags; } /// diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index 16fc555..fbaae95 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -229,8 +229,7 @@ namespace VNLib.Utils.Memory //If adding ref failed, the handle is closed ObjectDisposedException.ThrowIf(!addRef, this); - - //Create a new system.buffers memory handle from the offset ptr address + return new MemoryHandle(ptr, pinnable: this); } @@ -250,7 +249,10 @@ namespace VNLib.Utils.Memory /// public bool Equals(MemoryHandle? other) { - return other != null && (IsClosed | other.IsClosed) == false && _length == other._length && handle == other.handle; + return other != null + && (IsClosed | other.IsClosed) == false + && _length == other._length + && handle == other.handle; } /// diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index d10efc8..34489a4 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -143,7 +143,8 @@ namespace VNLib.Utils.Memory * get the heap's stats, otherwise return an empty handle */ return _lazyHeap.IsLoaded && _lazyHeap.Instance is TrackedHeapWrapper h - ? h.GetCurrentStats() : default; + ? h.GetCurrentStats() + : default; } /// diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs index 6e4f9b0..742346c 100644 --- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs +++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs @@ -34,7 +34,7 @@ namespace VNLib.Utils.Memory #region alloc [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool UseUnmanagedHeap(IUnmangedHeap heap, nuint elements) + private static bool CanUseUnmanagedHeap(IUnmangedHeap heap, nuint elements) { /* * We may allocate from the share heap only if the heap is not using locks @@ -123,7 +123,7 @@ namespace VNLib.Utils.Memory return default; } - return UseUnmanagedHeap(Shared, (uint)elements) + return CanUseUnmanagedHeap(Shared, (uint)elements) ? UnsafeAlloc(Shared, elements, zero) : UnsafeAlloc(ArrayPool.Shared, elements, zero); } @@ -229,7 +229,7 @@ namespace VNLib.Utils.Memory { ArgumentOutOfRangeException.ThrowIfNegative(elements); - if (UseUnmanagedHeap(Shared, elements)) + if (CanUseUnmanagedHeap(Shared, elements)) { return SafeAlloc(Shared, elements, zero); } @@ -425,7 +425,7 @@ namespace VNLib.Utils.Memory return default; } - return UseUnmanagedHeap(Shared, (uint)elements) + return CanUseUnmanagedHeap(Shared, (uint)elements) ? UnsafeAlloc(Shared, elements, zero) : UnsafeAlloc(ArrayPool.Shared, elements, zero); } @@ -462,7 +462,7 @@ namespace VNLib.Utils.Memory { ArgumentOutOfRangeException.ThrowIfNegative(elements); - return UseUnmanagedHeap(Shared, (uint)elements) + return CanUseUnmanagedHeap(Shared, (uint)elements) ? SafeAlloc(Shared, (nuint)elements, zero) : SafeAlloc(ArrayPool.Shared, elements, zero); } diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs index d93739d..bda8e2e 100644 --- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs +++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs @@ -29,8 +29,6 @@ using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; -using VNLib.Utils.Extensions; - namespace VNLib.Utils.Memory { @@ -62,16 +60,7 @@ namespace VNLib.Utils.Memory public readonly Span Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _handleType switch - { - HandleType.None => Span.Empty, - HandleType.Pool => _poolArr!.AsSpan(0, _length), - HandleType.PrivateHeap => MemoryUtil.GetSpan(_memoryPtr, _length), - _ => throw new InvalidOperationException("Invalid handle type"), - }; - } + get => AsSpan(); } /// @@ -153,7 +142,7 @@ namespace VNLib.Utils.Memory IntPtr unalloc = _memoryPtr; //Free the unmanaged handle bool unsafeFreed = _heap!.Free(ref unalloc); - Debug.Assert(unsafeFreed, "A previously allocated unsafe memhandle failed to free"); + Debug.Assert(unsafeFreed, "A previously allocated unsafe memhandle failed to free block"); } break; } @@ -193,6 +182,8 @@ namespace VNLib.Utils.Memory { switch (_handleType) { + case HandleType.None: + return ref Unsafe.NullRef(); case HandleType.Pool: return ref MemoryMarshal.GetArrayDataReference(_poolArr!); case HandleType.PrivateHeap: @@ -207,7 +198,7 @@ namespace VNLib.Utils.Memory /// /// The memory block that is held by the internl handle [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Span AsSpan() => Span; + public readonly Span AsSpan() => AsSpan(0, _length); /// /// Returns a that represents the memory block pointed to by this handle @@ -216,7 +207,7 @@ namespace VNLib.Utils.Memory /// The desired memory block at the desired element offset /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Span AsSpan(int start) => Span[start..]; + public readonly Span AsSpan(int start) => AsSpan(start, _length - start); /// /// Returns a that represents the memory block pointed to by this handle @@ -226,7 +217,23 @@ namespace VNLib.Utils.Memory /// The desired memory block at the desired element offset and length /// " [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Span AsSpan(int start, int length) => Span.Slice(start, length); + public readonly Span AsSpan(int start, int length) + { + ArgumentOutOfRangeException.ThrowIfNegative(start); + ArgumentOutOfRangeException.ThrowIfNegative(length); + ArgumentOutOfRangeException.ThrowIfGreaterThan(length - start, _length); + + /* + * If the handle is empty, a null ref should be returned. The + * check above will gaurd against calling this function on non-empty + * handles. So adding 0 to 0 on the reference should not cause any issues. + */ + + return MemoryMarshal.CreateSpan( + ref Unsafe.Add(ref GetReference(), start), + length + ); + } /// public readonly override int GetHashCode() @@ -248,7 +255,9 @@ namespace VNLib.Utils.Memory /// True if the other handle points to the same block of memory as the current handle public readonly bool Equals(in UnsafeMemoryHandle other) { - return _handleType == other._handleType && Length == other.Length && GetHashCode() == other.GetHashCode(); + return _handleType == other._handleType + && Length == other.Length + && GetHashCode() == other.GetHashCode(); } /// @@ -288,4 +297,4 @@ namespace VNLib.Utils.Memory public static bool operator !=(in UnsafeMemoryHandle left, in UnsafeMemoryHandle right) => !left.Equals(right); } -} \ No newline at end of file +} -- cgit