diff --git a/.gitignore b/.gitignore
index a595e413a..6d4d9c510 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,27 @@
/Distribution/rdrand.so
/Distribution/rdrand.dll
/Distribution/zlib.dll
+
+# LibUv Dependencies
+/Distribution/libuv.dll
+/Distribution/libuv.so
+/Distribution/Microsoft.AspNetCore.Connections.Abstractions.dll
+/Distribution/Microsoft.AspNetCore.Http.Features.dll
+/Distribution/Microsoft.Extensions.Configuration.Abstractions.dll
+/Distribution/Microsoft.Extensions.Configuration.Binder.dll
+/Distribution/Microsoft.Extensions.Configuration.dll
+/Distribution/Microsoft.Extensions.DependencyInjection.Abstractions.dll
+/Distribution/Microsoft.Extensions.DependencyInjection.dll
+/Distribution/Microsoft.Extensions.FileProviders.Abstractions.dll
+/Distribution/Microsoft.Extensions.Hosting.Abstractions.dll
+/Distribution/Microsoft.Extensions.Logging.Abstractions.dll
+/Distribution/Microsoft.Extensions.Logging.Configuration.dll
+/Distribution/Microsoft.Extensions.Logging.Console.dll
+/Distribution/Microsoft.Extensions.Logging.dll
+/Distribution/Microsoft.Extensions.Options.ConfigurationExtensions.dll
+/Distribution/Microsoft.Extensions.Options.dll
+/Distribution/Microsoft.Extensions.Primitives.dll
+
/Projects/Scripts/obj
/Projects/Scripts/bin
/Projects/Server/obj
diff --git a/Contributing.md b/Contributing.md
index 6a4faf938..64b88cc70 100644
--- a/Contributing.md
+++ b/Contributing.md
@@ -6,7 +6,7 @@ One of the easiest ways to contribute is to participate in discussions on GitHub
Start a discussion on the [repository issue tracker](https://github.com/modernuo/modernuo/issues).
## Reporting security issues and bugs
-Security issues and bugs should be reported privately, via email, to the hi@modernuo.com.
+Security issues and bugs should be reported privately, via email, to hi@modernuo.com.
You should receive a response within 24 hours.
If for some reason you do not, please follow up via email to ensure we received your original message.
@@ -35,7 +35,7 @@ If you don't know what a pull request is read this article: https://help.github.
configurations, environment variables, dependencies, etc.
1. Increase the version numbers to the new version that this Pull Request would represent.
The versioning scheme we use is [SemVer](http://semver.org/).
-1. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
+1. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
## Code of Conduct
diff --git a/Projects/Scripts/Accounting/AccessRestrictions.cs b/Projects/Scripts/Accounting/AccessRestrictions.cs
index ee40c4d10..49389abb8 100644
--- a/Projects/Scripts/Accounting/AccessRestrictions.cs
+++ b/Projects/Scripts/Accounting/AccessRestrictions.cs
@@ -16,7 +16,7 @@ namespace Server
{
try
{
- IPAddress ip = ((IPEndPoint)e.Socket.RemoteEndPoint).Address;
+ IPAddress ip = ((IPEndPoint)e.Context.RemoteEndPoint).Address;
if (Firewall.IsBlocked(ip))
{
@@ -43,4 +43,4 @@ namespace Server
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Projects/Scripts/Misc/ClientVerification.cs b/Projects/Scripts/Misc/ClientVerification.cs
index ea24ae0e5..eacc7aa6d 100644
--- a/Projects/Scripts/Misc/ClientVerification.cs
+++ b/Projects/Scripts/Misc/ClientVerification.cs
@@ -108,7 +108,7 @@ namespace Server.Misc
Timer.DelayCall(KickDelay, delegate
{
- if (state.Socket != null)
+ if (state.Connection != null)
{
Console.WriteLine("Client: {0}: Disconnecting, bad version", state);
state.Dispose();
@@ -173,4 +173,4 @@ namespace Server.Misc
Kick
}
}
-}
\ No newline at end of file
+}
diff --git a/Projects/Scripts/Misc/ServerList.cs b/Projects/Scripts/Misc/ServerList.cs
index 469fc8d84..86a6ea029 100644
--- a/Projects/Scripts/Misc/ServerList.cs
+++ b/Projects/Scripts/Misc/ServerList.cs
@@ -3,24 +3,25 @@ using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
+using Microsoft.AspNetCore.Connections;
using Server.Network;
namespace Server.Misc
{
public class ServerList
{
- /*
+ /*
* The default setting for Address, a value of 'null', will use your local IP address. If all of your local IP addresses
* are private network addresses and AutoDetect is 'true' then RunUO will attempt to discover your public IP address
* for you automatically.
*
* If you do not plan on allowing clients outside of your LAN to connect, you can set AutoDetect to 'false' and leave
* Address set to 'null'.
- *
+ *
* If your public IP address cannot be determined, you must change the value of Address to your public IP address
* manually to allow clients outside of your LAN to connect to your server. Address can be either an IP address or
* a hostname that will be resolved when RunUO starts.
- *
+ *
* If you want players outside your LAN to be able to connect to your server and you are behind a router, you must also
* forward TCP port 2593 to your private IP address. The procedure for doing this varies by manufacturer but generally
* involves configuration of the router through your web browser.
@@ -32,7 +33,7 @@ namespace Server.Misc
* properly and fully supports listening on multiple ports. If a client with a public IP address is connecting to a
* locally private address, the server will direct the client to either the AutoDetected IP address or the manually entered
* IP address or hostname, whichever is applicable. Loopback clients will be directed to loopback.
- *
+ *
* If you would like to listen on additional ports (i.e. 22, 23, 80, for clients behind highly restrictive egress
* firewalls) or specific IP adddresses you can do so by modifying the file SocketOptions.cs found in this directory.
*/
@@ -64,7 +65,7 @@ namespace Server.Misc
try
{
NetState ns = e.State;
- Socket s = ns.Socket;
+ ConnectionContext s = ns.Connection;
IPEndPoint ipep = (IPEndPoint)s.LocalEndPoint;
diff --git a/Projects/Scripts/Misc/SocketOptions.cs b/Projects/Scripts/Misc/SocketOptions.cs
index d51fb20e5..56d12f2d7 100644
--- a/Projects/Scripts/Misc/SocketOptions.cs
+++ b/Projects/Scripts/Misc/SocketOptions.cs
@@ -5,8 +5,6 @@ namespace Server
{
public class SocketOptions
{
- private const bool NagleEnabled = false; // Should the Nagle algorithm be enabled? This may reduce performance
-
private static IPEndPoint[] m_ListenerEndPoints =
{
new IPEndPoint(IPAddress.Any, 2593) // Default: Listen on port 2593 on all IP addresses
@@ -16,24 +14,10 @@ namespace Server
// new IPEndPoint( IPAddress.Parse( "1.2.3.4" ), 2593 ), // Listen on port 2593 on IP address 1.2.3.4
};
- public static void Initialize()
- {
- EventSink.SocketConnect += EventSink_SocketConnect;
- }
-
public static void RegisterListeners()
{
for (int i = 0; i < m_ListenerEndPoints.Length; i++)
Core.MessagePump.AddListener(m_ListenerEndPoints[i]);
}
-
- private static void EventSink_SocketConnect(SocketConnectEventArgs e)
- {
- if (!e.AllowConnection)
- return;
-
- if (!NagleEnabled)
- e.Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1); // RunUO uses its own algorithm
- }
}
}
diff --git a/Projects/Scripts/Scripts.csproj b/Projects/Scripts/Scripts.csproj
index f3b49987e..e7fa412b8 100644
--- a/Projects/Scripts/Scripts.csproj
+++ b/Projects/Scripts/Scripts.csproj
@@ -35,4 +35,7 @@
false
+
+
+
diff --git a/Projects/Scripts/SpecialSystems/Engines/PreventInaccess.cs b/Projects/Scripts/SpecialSystems/Engines/PreventInaccess.cs
index 8d81d45fb..fcde69079 100644
--- a/Projects/Scripts/SpecialSystems/Engines/PreventInaccess.cs
+++ b/Projects/Scripts/SpecialSystems/Engines/PreventInaccess.cs
@@ -63,7 +63,7 @@ namespace Server.Misc
}
}
- private static bool HasDisconnected(Mobile m) => m.NetState?.Socket == null;
+ private static bool HasDisconnected(Mobile m) => m.NetState?.Connection == null;
private static LocationInfo GetRandomDestination() => m_Destinations[Utility.Random(m_Destinations.Length)];
diff --git a/Projects/Server/ApplicationLifetime.cs b/Projects/Server/ApplicationLifetime.cs
new file mode 100644
index 000000000..314cdcdea
--- /dev/null
+++ b/Projects/Server/ApplicationLifetime.cs
@@ -0,0 +1,108 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+ ///
+ /// Allows consumers to perform cleanup during a graceful shutdown.
+ ///
+ internal class ApplicationLifetime : IHostApplicationLifetime
+ {
+ private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
+ private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
+ private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
+ private readonly ILogger _logger;
+
+ public ApplicationLifetime(ILogger logger) => _logger = logger;
+
+ ///
+ /// Triggered when the application host has fully started and is about to wait
+ /// for a graceful shutdown.
+ ///
+ public CancellationToken ApplicationStarted => _startedSource.Token;
+
+ ///
+ /// Triggered when the application host is performing a graceful shutdown.
+ /// Request may still be in flight. Shutdown will block until this event completes.
+ ///
+ public CancellationToken ApplicationStopping => _stoppingSource.Token;
+
+ ///
+ /// Triggered when the application host is performing a graceful shutdown.
+ /// All requests should be complete at this point. Shutdown will block
+ /// until this event completes.
+ ///
+ public CancellationToken ApplicationStopped => _stoppedSource.Token;
+
+ ///
+ /// Signals the ApplicationStopping event and blocks until it completes.
+ ///
+ public void StopApplication()
+ {
+ // Lock on CTS to synchronize multiple calls to StopApplication. This guarantees that the first call
+ // to StopApplication and its callbacks run to completion before subsequent calls to StopApplication,
+ // which will no-op since the first call already requested cancellation, get a chance to execute.
+ lock (_stoppingSource)
+ {
+ try
+ {
+ ExecuteHandlers(_stoppingSource);
+ }
+ catch (Exception)
+ {
+ /* _logger.ApplicationError(LoggerEventIds.ApplicationStoppingException,
+ "An error occurred stopping the application",
+ ex);*/
+ }
+ }
+ }
+
+ ///
+ /// Signals the ApplicationStarted event and blocks until it completes.
+ ///
+ public void NotifyStarted()
+ {
+ try
+ {
+ ExecuteHandlers(_startedSource);
+ }
+ catch (Exception)
+ {
+ /* _logger.ApplicationError(LoggerEventIds.ApplicationStartupException,
+ "An error occurred starting the application",
+ ex);*/
+ }
+ }
+
+ ///
+ /// Signals the ApplicationStopped event and blocks until it completes.
+ ///
+ public void NotifyStopped()
+ {
+ try
+ {
+ ExecuteHandlers(_stoppedSource);
+ }
+ catch (Exception)
+ {
+ /* _logger.ApplicationError(LoggerEventIds.ApplicationStoppedException,
+ "An error occurred stopping the application",
+ ex);*/
+ }
+ }
+
+ private void ExecuteHandlers(CancellationTokenSource cancel)
+ {
+ // Noop if this is already cancelled
+ if (cancel.IsCancellationRequested) return;
+
+ // Run the cancellation token callbacks
+ cancel.Cancel(false);
+ }
+ }
+}
diff --git a/Projects/Server/Assemblies/libuv.dll b/Projects/Server/Assemblies/libuv.dll
new file mode 100644
index 000000000..d744f343d
Binary files /dev/null and b/Projects/Server/Assemblies/libuv.dll differ
diff --git a/Projects/Server/Assemblies/libuv.linux.so b/Projects/Server/Assemblies/libuv.linux.so
new file mode 100755
index 000000000..d9caeba12
Binary files /dev/null and b/Projects/Server/Assemblies/libuv.linux.so differ
diff --git a/Projects/Server/Assemblies/libuv.osx.so b/Projects/Server/Assemblies/libuv.osx.so
new file mode 100755
index 000000000..8b98042f1
Binary files /dev/null and b/Projects/Server/Assemblies/libuv.osx.so differ
diff --git a/Projects/Server/Buffers/DiagnosticMemoryPool.cs b/Projects/Server/Buffers/DiagnosticMemoryPool.cs
new file mode 100644
index 000000000..9f032a571
--- /dev/null
+++ b/Projects/Server/Buffers/DiagnosticMemoryPool.cs
@@ -0,0 +1,132 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace System.Buffers
+{
+ ///
+ /// Used to allocate and distribute re-usable blocks of memory.
+ ///
+ public class DiagnosticMemoryPool : MemoryPool
+ {
+ private readonly MemoryPool _pool;
+
+ private readonly bool _allowLateReturn;
+
+ private readonly bool _rentTracking;
+
+ private readonly object _syncObj;
+
+ private readonly HashSet _blocks;
+
+ private readonly List _blockAccessExceptions;
+
+ private readonly TaskCompletionSource _allBlocksReturned;
+
+ private int _totalBlocks;
+
+ ///
+ /// This default value passed in to Rent to use the default value for the pool.
+ ///
+ private const int AnySize = -1;
+
+ public DiagnosticMemoryPool(MemoryPool pool, bool allowLateReturn = false, bool rentTracking = false)
+ {
+ _pool = pool;
+ _allowLateReturn = allowLateReturn;
+ _rentTracking = rentTracking;
+ _blocks = new HashSet();
+ _syncObj = new object();
+ _allBlocksReturned = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ _blockAccessExceptions = new List();
+ }
+
+ public bool IsDisposed { get; private set; }
+
+ public override IMemoryOwner Rent(int size = AnySize)
+ {
+ lock (_syncObj)
+ {
+ if (IsDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
+
+ var diagnosticPoolBlock = new DiagnosticPoolBlock(this, _pool.Rent(size));
+ if (_rentTracking) diagnosticPoolBlock.Track();
+ _totalBlocks++;
+ _blocks.Add(diagnosticPoolBlock);
+ return diagnosticPoolBlock;
+ }
+ }
+
+ public override int MaxBufferSize => _pool.MaxBufferSize;
+
+ internal void Return(DiagnosticPoolBlock block)
+ {
+ bool returnedAllBlocks;
+ lock (_syncObj)
+ {
+ _blocks.Remove(block);
+ returnedAllBlocks = _blocks.Count == 0;
+ }
+
+ if (IsDisposed)
+ {
+ if (!_allowLateReturn) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockReturnedToDisposedPool(block);
+
+ if (returnedAllBlocks) SetAllBlocksReturned();
+ }
+
+ }
+
+ internal void ReportException(Exception exception)
+ {
+ lock (_syncObj)
+ {
+ _blockAccessExceptions.Add(exception);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_DoubleDispose();
+
+ bool allBlocksReturned = false;
+ try
+ {
+ lock (_syncObj)
+ {
+ IsDisposed = true;
+ allBlocksReturned = _blocks.Count == 0;
+ if (!allBlocksReturned && !_allowLateReturn) MemoryPoolThrowHelper.ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
+
+ if (_blockAccessExceptions.Any()) throw CreateAccessExceptions();
+ }
+ }
+ finally
+ {
+ if (allBlocksReturned) SetAllBlocksReturned();
+ }
+ }
+
+ private void SetAllBlocksReturned()
+ {
+ if (_blockAccessExceptions.Any())
+ _allBlocksReturned.SetException(CreateAccessExceptions());
+ else
+ _allBlocksReturned.SetResult(null);
+ }
+
+ private AggregateException CreateAccessExceptions() => new AggregateException("Exceptions occurred while accessing blocks", _blockAccessExceptions.ToArray());
+
+ public async Task WhenAllBlocksReturnedAsync(TimeSpan timeout)
+ {
+ var task = await Task.WhenAny(_allBlocksReturned.Task, Task.Delay(timeout));
+ if (task != _allBlocksReturned.Task)
+ MemoryPoolThrowHelper.ThrowInvalidOperationException_BlocksWereNotReturnedInTime(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray());
+
+ await task;
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/DiagnosticPoolBlock.cs b/Projects/Server/Buffers/DiagnosticPoolBlock.cs
new file mode 100644
index 000000000..9623596f8
--- /dev/null
+++ b/Projects/Server/Buffers/DiagnosticPoolBlock.cs
@@ -0,0 +1,188 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Threading;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.Buffers
+{
+ ///
+ /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independent array segments.
+ ///
+ public sealed class DiagnosticPoolBlock : MemoryManager
+ {
+ ///
+ /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
+ ///
+ private readonly DiagnosticMemoryPool _pool;
+
+ private readonly IMemoryOwner _memoryOwner;
+ private MemoryHandle? _memoryHandle;
+ private Memory _memory;
+
+ private readonly object _syncObj = new object();
+ private bool _isDisposed;
+ private int _pinCount;
+
+
+ ///
+ /// This object cannot be instantiated outside of the static Create method
+ ///
+ internal DiagnosticPoolBlock(DiagnosticMemoryPool pool, IMemoryOwner memoryOwner)
+ {
+ _pool = pool;
+ _memoryOwner = memoryOwner;
+ _memory = memoryOwner.Memory;
+ }
+
+ public override Memory Memory
+ {
+ get
+ {
+ try
+ {
+ lock (_syncObj)
+ {
+ if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
+
+ if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
+
+ return CreateMemory(_memory.Length);
+ }
+ }
+ catch (Exception exception)
+ {
+ _pool.ReportException(exception);
+ throw;
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ try
+ {
+ lock (_syncObj)
+ {
+ if (Volatile.Read(ref _pinCount) > 0) MemoryPoolThrowHelper.ThrowInvalidOperationException_ReturningPinnedBlock(this);
+
+ if (_isDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockDoubleDispose(this);
+
+ _memoryOwner.Dispose();
+
+ _pool.Return(this);
+
+ _isDisposed = true;
+ }
+ }
+ catch (Exception exception)
+ {
+ _pool.ReportException(exception);
+ throw;
+ }
+ }
+
+ public override Span GetSpan()
+ {
+ try
+ {
+ lock (_syncObj)
+ {
+ if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
+
+ if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
+
+ return _memory.Span;
+ }
+ }
+ catch (Exception exception)
+ {
+ _pool.ReportException(exception);
+ throw;
+ }
+ }
+
+ public override MemoryHandle Pin(int byteOffset = 0)
+ {
+ try
+ {
+ lock (_syncObj)
+ {
+ if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
+
+ if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
+
+ if (byteOffset < 0 || byteOffset > _memory.Length) MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException(_memory.Length, byteOffset);
+
+ _pinCount++;
+
+ _memoryHandle ??= _memory.Pin();
+
+ unsafe
+ {
+ return new MemoryHandle(((IntPtr)_memoryHandle.Value.Pointer + byteOffset).ToPointer(), default, this);
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ _pool.ReportException(exception);
+ throw;
+ }
+ }
+
+ protected override bool TryGetArray(out ArraySegment segment)
+ {
+ try
+ {
+ lock (_syncObj)
+ {
+ if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPoolBlock);
+
+ if (_pool.IsDisposed) MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(this);
+
+ return MemoryMarshal.TryGetArray(_memory, out segment);
+ }
+ }
+ catch (Exception exception)
+ {
+ _pool.ReportException(exception);
+ throw;
+ }
+ }
+
+ public override void Unpin()
+ {
+ try
+ {
+ lock (_syncObj)
+ {
+ if (_pinCount == 0) MemoryPoolThrowHelper.ThrowInvalidOperationException_PinCountZero(this);
+
+ _pinCount--;
+
+ if (_pinCount == 0)
+ {
+ Debug.Assert(_memoryHandle.HasValue);
+ _memoryHandle.Value.Dispose();
+ _memoryHandle = null;
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ _pool.ReportException(exception);
+ throw;
+ }
+ }
+
+ public StackTrace Leaser { get; set; }
+
+ public void Track()
+ {
+ Leaser = new StackTrace(false);
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/MemoryPoolBlock.cs b/Projects/Server/Buffers/MemoryPoolBlock.cs
new file mode 100644
index 000000000..2f1b87e4c
--- /dev/null
+++ b/Projects/Server/Buffers/MemoryPoolBlock.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Buffers
+{
+ ///
+ /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independent array segments.
+ ///
+ public sealed class MemoryPoolBlock : IMemoryOwner
+ {
+ private readonly int _offset;
+ private readonly int _length;
+
+ ///
+ /// This object cannot be instantiated outside of the static Create method
+ ///
+ internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
+ {
+ _offset = offset;
+ _length = length;
+
+ Pool = pool;
+ Slab = slab;
+
+ Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length);
+ }
+
+ ///
+ /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
+ ///
+ public SlabMemoryPool Pool { get; }
+
+ ///
+ /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
+ ///
+ public MemoryPoolSlab Slab { get; }
+
+ public Memory Memory { get; }
+
+ ~MemoryPoolBlock()
+ {
+ Pool.RefreshBlock(Slab, _offset, _length);
+ }
+
+ public void Dispose()
+ {
+ Pool.Return(this);
+ }
+
+ public void Lease()
+ {
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/MemoryPoolFactory.cs b/Projects/Server/Buffers/MemoryPoolFactory.cs
new file mode 100644
index 000000000..f8a3f37c5
--- /dev/null
+++ b/Projects/Server/Buffers/MemoryPoolFactory.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace System.Buffers
+{
+ public static class SlabMemoryPoolFactory
+ {
+ public static MemoryPool Create()
+ {
+#if DEBUG
+ return new DiagnosticMemoryPool(CreateSlabMemoryPool());
+#else
+ return CreateSlabMemoryPool();
+#endif
+ }
+
+ public static MemoryPool CreateSlabMemoryPool() => new SlabMemoryPool();
+ }
+}
diff --git a/Projects/Server/Buffers/MemoryPoolSlab.cs b/Projects/Server/Buffers/MemoryPoolSlab.cs
new file mode 100644
index 000000000..38be52e5b
--- /dev/null
+++ b/Projects/Server/Buffers/MemoryPoolSlab.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.InteropServices;
+
+namespace System.Buffers
+{
+ ///
+ /// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independent array segments.
+ ///
+ public class MemoryPoolSlab : IDisposable
+ {
+ ///
+ /// This handle pins the managed array in memory until the slab is disposed. This prevents it from being
+ /// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls.
+ ///
+ private GCHandle _gcHandle;
+ private bool _isDisposed;
+
+ public MemoryPoolSlab(byte[] data)
+ {
+ Array = data;
+ _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
+ NativePointer = _gcHandle.AddrOfPinnedObject();
+ }
+
+ ///
+ /// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the
+ /// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the
+ /// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will
+ /// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage
+ /// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will
+ /// be unpinned by the slab's Dispose.
+ ///
+ public bool IsActive => !_isDisposed;
+
+ public IntPtr NativePointer { get; private set; }
+
+ public byte[] Array { get; private set; }
+
+ public static MemoryPoolSlab Create(int length)
+ {
+ // allocate and pin requested memory length
+ var array = new byte[length];
+
+ // allocate and return slab tracking object
+ return new MemoryPoolSlab(array);
+ }
+
+ protected void Dispose(bool disposing)
+ {
+ if (_isDisposed) return;
+
+ _isDisposed = true;
+
+ Array = null;
+ NativePointer = IntPtr.Zero;;
+
+ if (_gcHandle.IsAllocated) _gcHandle.Free();
+ }
+
+ ~MemoryPoolSlab()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/MemoryPoolThrowHelper.cs b/Projects/Server/Buffers/MemoryPoolThrowHelper.cs
new file mode 100644
index 000000000..cf6284148
--- /dev/null
+++ b/Projects/Server/Buffers/MemoryPoolThrowHelper.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System.Buffers
+{
+ public class MemoryPoolThrowHelper
+ {
+ public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset)
+ {
+ throw GetArgumentOutOfRangeException(sourceLength, offset);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset) =>
+ (uint)offset > (uint)sourceLength ? new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset)) : new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length));
+ public static void ThrowInvalidOperationException_PinCountZero(DiagnosticPoolBlock block)
+ {
+ throw new InvalidOperationException(GenerateMessage("Can't unpin, pin count is zero", block));
+ }
+
+ public static void ThrowInvalidOperationException_ReturningPinnedBlock(DiagnosticPoolBlock block)
+ {
+ throw new InvalidOperationException(GenerateMessage("Disposing pinned block", block));
+ }
+
+ public static void ThrowInvalidOperationException_DoubleDispose()
+ {
+ throw new InvalidOperationException("Object is being disposed twice");
+ }
+
+ public static void ThrowInvalidOperationException_BlockDoubleDispose(DiagnosticPoolBlock block)
+ {
+ throw new InvalidOperationException("Block is being disposed twice");
+ }
+
+ public static void ThrowInvalidOperationException_BlockReturnedToDisposedPool(DiagnosticPoolBlock block)
+ {
+ throw new InvalidOperationException(GenerateMessage("Block is being returned to disposed pool", block));
+ }
+
+ public static void ThrowInvalidOperationException_BlockIsBackedByDisposedSlab(DiagnosticPoolBlock block)
+ {
+ throw new InvalidOperationException(GenerateMessage("Block is backed by disposed slab", block));
+ }
+
+ public static void ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(int returned, int total, DiagnosticPoolBlock[] blocks)
+ {
+ throw new InvalidOperationException(GenerateMessage($"Memory pool with active blocks is being disposed, {returned} of {total} returned", blocks));
+ }
+
+ public static void ThrowInvalidOperationException_BlocksWereNotReturnedInTime(int returned, int total, DiagnosticPoolBlock[] blocks)
+ {
+ throw new InvalidOperationException(GenerateMessage($"Blocks were not returned in time, {returned} of {total} returned ", blocks));
+ }
+
+ private static string GenerateMessage(string message, params DiagnosticPoolBlock[] blocks)
+ {
+ StringBuilder builder = new StringBuilder(message);
+ foreach (var diagnosticPoolBlock in blocks)
+ if (diagnosticPoolBlock.Leaser != null)
+ {
+ builder.AppendLine();
+
+ builder.AppendLine("Block leased from:");
+ builder.AppendLine(diagnosticPoolBlock.Leaser.ToString());
+ }
+
+ return builder.ToString();
+ }
+
+ public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
+ {
+ throw GetArgumentOutOfRangeException_BufferRequestTooLarge(maxSize);
+ }
+
+ public static void ThrowObjectDisposedException(ExceptionArgument argument)
+ {
+ throw GetObjectDisposedException(argument);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) => new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.size), $"Cannot allocate more than {maxSize} bytes in a single buffer");
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ObjectDisposedException GetObjectDisposedException(ExceptionArgument argument) => new ObjectDisposedException(GetArgumentName(argument));
+
+ private static string GetArgumentName(ExceptionArgument argument)
+ {
+ Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument), "The enum value is not defined, please check the ExceptionArgument Enum.");
+
+ return argument.ToString();
+ }
+
+ public enum ExceptionArgument
+ {
+ size,
+ offset,
+ length,
+ MemoryPoolBlock,
+ MemoryPool
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/SlabMemoryPool.cs b/Projects/Server/Buffers/SlabMemoryPool.cs
new file mode 100644
index 000000000..acde6b894
--- /dev/null
+++ b/Projects/Server/Buffers/SlabMemoryPool.cs
@@ -0,0 +1,189 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Threading;
+
+namespace System.Buffers
+{
+ ///
+ /// Used to allocate and distribute re-usable blocks of memory.
+ ///
+ public sealed class SlabMemoryPool : MemoryPool
+ {
+ ///
+ /// The size of a block. 4096 is chosen because most operating systems use 4k pages.
+ ///
+ private const int _blockSize = 4096;
+
+ ///
+ /// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory
+ /// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively
+ /// affect memory management's compactification.
+ ///
+ private const int _blockCount = 32;
+
+ ///
+ /// Max allocation block size for pooled blocks,
+ /// larger values can be leased but they will be disposed after use rather than returned to the pool.
+ ///
+ public override int MaxBufferSize { get; } = _blockSize;
+
+ ///
+ /// The size of a block. 4096 is chosen because most operating systems use 4k pages.
+ ///
+ public static int BlockSize => _blockSize;
+
+ ///
+ /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
+ ///
+ private static readonly int _slabLength = _blockSize * _blockCount;
+
+ ///
+ /// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects
+ /// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added.
+ ///
+ private readonly ConcurrentQueue _blocks = new ConcurrentQueue();
+
+ ///
+ /// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive,
+ /// the blocks will be added to _blocks when returned.
+ ///
+ private readonly ConcurrentStack _slabs = new ConcurrentStack();
+
+ ///
+ /// This is part of implementing the IDisposable pattern.
+ ///
+ private bool _isDisposed; // To detect redundant calls
+
+ private int _totalAllocatedBlocks;
+
+ private readonly object _disposeSync = new object();
+
+ ///
+ /// This default value passed in to Rent to use the default value for the pool.
+ ///
+ private const int AnySize = -1;
+
+ public override IMemoryOwner Rent(int size = AnySize)
+ {
+ if (size > _blockSize) MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize);
+
+ var block = Lease();
+ return block;
+ }
+
+ ///
+ /// Called to take a block from the pool.
+ ///
+ /// The block that is reserved for the called. It must be passed to Return when it is no longer being used.
+ private MemoryPoolBlock Lease()
+ {
+ if (_isDisposed) MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
+
+ if (_blocks.TryDequeue(out MemoryPoolBlock block))
+ {
+ // block successfully taken from the stack - return it
+
+ block.Lease();
+ return block;
+ }
+ // no blocks available - grow the pool
+ block = AllocateSlab();
+ block.Lease();
+ return block;
+ }
+
+ ///
+ /// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
+ /// block tracking objects, and adds them all to the pool.
+ ///
+ private MemoryPoolBlock AllocateSlab()
+ {
+ var slab = MemoryPoolSlab.Create(_slabLength);
+ _slabs.Push(slab);
+
+ var basePtr = slab.NativePointer;
+ // Page align the blocks
+ var offset = (int)((((ulong)basePtr + (uint)_blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr);
+ // Ensure page aligned
+ Debug.Assert(((ulong)basePtr + (uint)offset) % _blockSize == 0);
+
+ var blockCount = (_slabLength - offset) / _blockSize;
+ Interlocked.Add(ref _totalAllocatedBlocks, blockCount);
+
+ MemoryPoolBlock block = null;
+
+ for (int i = 0; i < blockCount; i++)
+ {
+ block = new MemoryPoolBlock(this, slab, offset, _blockSize);
+
+ if (i != blockCount - 1) // last block
+ {
+#if BLOCK_LEASE_TRACKING
+ block.IsLeased = true;
+#endif
+ Return(block);
+ }
+
+ offset += _blockSize;
+ }
+
+ return block;
+ }
+
+ ///
+ /// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and
+ /// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the
+ /// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and return
+ /// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to avoid
+ /// leaving "dead zones" in the slab due to lost block tracking objects.
+ ///
+ /// The block to return. It must have been acquired by calling Lease on the same memory pool instance.
+ internal void Return(MemoryPoolBlock block)
+ {
+#if BLOCK_LEASE_TRACKING
+ Debug.Assert(block.Pool == this, "Returned block was not leased from this pool");
+ Debug.Assert(block.IsLeased, $"Block being returned to pool twice: {block.Leaser}{Environment.NewLine}");
+ block.IsLeased = false;
+#endif
+
+ if (!_isDisposed)
+ _blocks.Enqueue(block);
+ else
+ GC.SuppressFinalize(block);
+ }
+
+ // This method can ONLY be called from the finalizer of MemoryPoolBlock
+ internal void RefreshBlock(MemoryPoolSlab slab, int offset, int length)
+ {
+ lock (_disposeSync)
+ {
+ if (!_isDisposed && slab?.IsActive == true)
+ // Need to make a new object because this one is being finalized
+ // Note, this must be called within the _disposeSync lock because the block
+ // could be disposed at the same time as the finalizer.
+ Return(new MemoryPoolBlock(this, slab, offset, length));
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed) return;
+
+ lock (_disposeSync)
+ {
+ _isDisposed = true;
+
+ if (disposing)
+ while (_slabs.TryPop(out MemoryPoolSlab slab))
+ // dispose managed state (managed objects).
+ slab.Dispose();
+
+ // Discard blocks in pool
+ while (_blocks.TryDequeue(out MemoryPoolBlock block)) GC.SuppressFinalize(block);
+ }
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/SpanWriter.cs b/Projects/Server/Buffers/SpanWriter.cs
index 8634d9e75..4103f462d 100644
--- a/Projects/Server/Buffers/SpanWriter.cs
+++ b/Projects/Server/Buffers/SpanWriter.cs
@@ -1,22 +1,22 @@
-/***************************************************************************
- * SpanWriter.cs
- * -------------------
- * begin : August 5, 2019
- * copyright : (C) The ModernUO Team
- * email : hi@modernuo.com
- *
- * $Id$
- *
- ***************************************************************************/
-
-/***************************************************************************
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- ***************************************************************************/
+/*************************************************************************
+ * ModernUO *
+ * Copyright (C) 2019 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: SpanWriter.cs - Created: 2019/08/05 - Updated: 2019/12/24 *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
using System;
using System.Text;
diff --git a/Projects/Server/EventSink.cs b/Projects/Server/EventSink.cs
index 0d12f3433..f93416e65 100644
--- a/Projects/Server/EventSink.cs
+++ b/Projects/Server/EventSink.cs
@@ -22,6 +22,7 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
+using Microsoft.AspNetCore.Connections;
using Server.Accounting;
using Server.Guilds;
using Server.Network;
@@ -380,13 +381,13 @@ namespace Server
public class SocketConnectEventArgs : EventArgs
{
- public SocketConnectEventArgs(Socket s)
+ public SocketConnectEventArgs(ConnectionContext c)
{
- Socket = s;
+ Context = c;
AllowConnection = true;
}
- public Socket Socket{ get; }
+ public ConnectionContext Context{ get; }
public bool AllowConnection{ get; set; }
}
diff --git a/Projects/Server/Kestrel/CorrelationIdGenerator.cs b/Projects/Server/Kestrel/CorrelationIdGenerator.cs
new file mode 100644
index 000000000..1be328db1
--- /dev/null
+++ b/Projects/Server/Kestrel/CorrelationIdGenerator.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Connections
+{
+ public static class CorrelationIdGenerator
+ {
+ // Base32 encoding - in ascii sort order for easy text based sorting
+ private static readonly char[] s_encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV".ToCharArray();
+
+ // Seed the _lastConnectionId for this application instance with
+ // the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001
+ // for a roughly increasing _lastId over restarts
+ private static long _lastId = DateTime.UtcNow.Ticks;
+
+ public static string GetNextId() => GenerateId(Interlocked.Increment(ref _lastId));
+
+ private static string GenerateId(long id)
+ {
+ return string.Create(13, id, (buffer, value) =>
+ {
+ char[] encode32Chars = s_encode32Chars;
+
+ buffer[12] = encode32Chars[value & 31];
+ buffer[11] = encode32Chars[(value >> 5) & 31];
+ buffer[10] = encode32Chars[(value >> 10) & 31];
+ buffer[9] = encode32Chars[(value >> 15) & 31];
+ buffer[8] = encode32Chars[(value >> 20) & 31];
+ buffer[7] = encode32Chars[(value >> 25) & 31];
+ buffer[6] = encode32Chars[(value >> 30) & 31];
+ buffer[5] = encode32Chars[(value >> 35) & 31];
+ buffer[4] = encode32Chars[(value >> 40) & 31];
+ buffer[3] = encode32Chars[(value >> 45) & 31];
+ buffer[2] = encode32Chars[(value >> 50) & 31];
+ buffer[1] = encode32Chars[(value >> 55) & 31];
+ buffer[0] = encode32Chars[(value >> 60) & 31];
+ });
+ }
+ }
+}
diff --git a/Projects/Server/Kestrel/TransportConnection.FeatureCollection.cs b/Projects/Server/Kestrel/TransportConnection.FeatureCollection.cs
new file mode 100644
index 000000000..70033b7c9
--- /dev/null
+++ b/Projects/Server/Kestrel/TransportConnection.FeatureCollection.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Threading;
+using Microsoft.AspNetCore.Connections.Features;
+
+namespace Microsoft.AspNetCore.Connections
+{
+ public partial class TransportConnection : IConnectionIdFeature,
+ IConnectionTransportFeature,
+ IConnectionItemsFeature,
+ IMemoryPoolFeature,
+ IConnectionLifetimeFeature
+ {
+ // NOTE: When feature interfaces are added to or removed from this TransportConnection class implementation,
+ // then the list of `features` in the generated code project MUST also be updated.
+ // See also: tools/CodeGenerator/TransportConnectionFeatureCollection.cs
+
+ MemoryPool IMemoryPoolFeature.MemoryPool => MemoryPool;
+
+ IDuplexPipe IConnectionTransportFeature.Transport
+ {
+ get => Transport;
+ set => Transport = value;
+ }
+
+ IDictionary IConnectionItemsFeature.Items
+ {
+ get => Items;
+ set => Items = value;
+ }
+
+ CancellationToken IConnectionLifetimeFeature.ConnectionClosed
+ {
+ get => ConnectionClosed;
+ set => ConnectionClosed = value;
+ }
+
+ void IConnectionLifetimeFeature.Abort() => Abort(new ConnectionAbortedException("The connection was aborted by the application via IConnectionLifetimeFeature.Abort()."));
+ }
+}
diff --git a/Projects/Server/Kestrel/TransportConnection.Generated.cs b/Projects/Server/Kestrel/TransportConnection.Generated.cs
new file mode 100644
index 000000000..eb6f2ba25
--- /dev/null
+++ b/Projects/Server/Kestrel/TransportConnection.Generated.cs
@@ -0,0 +1,248 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+using Microsoft.AspNetCore.Connections.Features;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Connections
+{
+ public partial class TransportConnection : IFeatureCollection
+ {
+ private static readonly Type IConnectionIdFeatureType = typeof(IConnectionIdFeature);
+ private static readonly Type IConnectionTransportFeatureType = typeof(IConnectionTransportFeature);
+ private static readonly Type IConnectionItemsFeatureType = typeof(IConnectionItemsFeature);
+ private static readonly Type IMemoryPoolFeatureType = typeof(IMemoryPoolFeature);
+ private static readonly Type IConnectionLifetimeFeatureType = typeof(IConnectionLifetimeFeature);
+
+ private object _currentIConnectionIdFeature;
+ private object _currentIConnectionTransportFeature;
+ private object _currentIConnectionItemsFeature;
+ private object _currentIMemoryPoolFeature;
+ private object _currentIConnectionLifetimeFeature;
+
+ private int _featureRevision;
+
+ private List> MaybeExtra;
+
+ private void FastReset()
+ {
+ _currentIConnectionIdFeature = this;
+ _currentIConnectionTransportFeature = this;
+ _currentIConnectionItemsFeature = this;
+ _currentIMemoryPoolFeature = this;
+ _currentIConnectionLifetimeFeature = this;
+
+ }
+
+ // Internal for testing
+ internal void ResetFeatureCollection()
+ {
+ FastReset();
+ MaybeExtra?.Clear();
+ _featureRevision++;
+ }
+
+ private object ExtraFeatureGet(Type key)
+ {
+ if (MaybeExtra == null)
+ {
+ return null;
+ }
+ for (var i = 0; i < MaybeExtra.Count; i++)
+ {
+ var kv = MaybeExtra[i];
+ if (kv.Key == key)
+ {
+ return kv.Value;
+ }
+ }
+ return null;
+ }
+
+ private void ExtraFeatureSet(Type key, object value)
+ {
+ if (MaybeExtra == null)
+ {
+ MaybeExtra = new List>(2);
+ }
+
+ for (var i = 0; i < MaybeExtra.Count; i++)
+ {
+ if (MaybeExtra[i].Key == key)
+ {
+ MaybeExtra[i] = new KeyValuePair(key, value);
+ return;
+ }
+ }
+ MaybeExtra.Add(new KeyValuePair(key, value));
+ }
+
+ bool IFeatureCollection.IsReadOnly => false;
+
+ int IFeatureCollection.Revision => _featureRevision;
+
+ object IFeatureCollection.this[Type key]
+ {
+ get
+ {
+ object feature = null;
+ if (key == IConnectionIdFeatureType)
+ {
+ feature = _currentIConnectionIdFeature;
+ }
+ else if (key == IConnectionTransportFeatureType)
+ {
+ feature = _currentIConnectionTransportFeature;
+ }
+ else if (key == IConnectionItemsFeatureType)
+ {
+ feature = _currentIConnectionItemsFeature;
+ }
+ else if (key == IMemoryPoolFeatureType)
+ {
+ feature = _currentIMemoryPoolFeature;
+ }
+ else if (key == IConnectionLifetimeFeatureType)
+ {
+ feature = _currentIConnectionLifetimeFeature;
+ }
+ else if (MaybeExtra != null)
+ {
+ feature = ExtraFeatureGet(key);
+ }
+
+ return feature;
+ }
+
+ set
+ {
+ _featureRevision++;
+
+ if (key == IConnectionIdFeatureType)
+ {
+ _currentIConnectionIdFeature = value;
+ }
+ else if (key == IConnectionTransportFeatureType)
+ {
+ _currentIConnectionTransportFeature = value;
+ }
+ else if (key == IConnectionItemsFeatureType)
+ {
+ _currentIConnectionItemsFeature = value;
+ }
+ else if (key == IMemoryPoolFeatureType)
+ {
+ _currentIMemoryPoolFeature = value;
+ }
+ else if (key == IConnectionLifetimeFeatureType)
+ {
+ _currentIConnectionLifetimeFeature = value;
+ }
+ else
+ {
+ ExtraFeatureSet(key, value);
+ }
+ }
+ }
+
+ TFeature IFeatureCollection.Get()
+ {
+ TFeature feature = default;
+ if (typeof(TFeature) == typeof(IConnectionIdFeature))
+ {
+ feature = (TFeature)_currentIConnectionIdFeature;
+ }
+ else if (typeof(TFeature) == typeof(IConnectionTransportFeature))
+ {
+ feature = (TFeature)_currentIConnectionTransportFeature;
+ }
+ else if (typeof(TFeature) == typeof(IConnectionItemsFeature))
+ {
+ feature = (TFeature)_currentIConnectionItemsFeature;
+ }
+ else if (typeof(TFeature) == typeof(IMemoryPoolFeature))
+ {
+ feature = (TFeature)_currentIMemoryPoolFeature;
+ }
+ else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature))
+ {
+ feature = (TFeature)_currentIConnectionLifetimeFeature;
+ }
+ else if (MaybeExtra != null)
+ {
+ feature = (TFeature)(ExtraFeatureGet(typeof(TFeature)));
+ }
+
+ return feature;
+ }
+
+ void IFeatureCollection.Set(TFeature feature)
+ {
+ _featureRevision++;
+ if (typeof(TFeature) == typeof(IConnectionIdFeature))
+ {
+ _currentIConnectionIdFeature = feature;
+ }
+ else if (typeof(TFeature) == typeof(IConnectionTransportFeature))
+ {
+ _currentIConnectionTransportFeature = feature;
+ }
+ else if (typeof(TFeature) == typeof(IConnectionItemsFeature))
+ {
+ _currentIConnectionItemsFeature = feature;
+ }
+ else if (typeof(TFeature) == typeof(IMemoryPoolFeature))
+ {
+ _currentIMemoryPoolFeature = feature;
+ }
+ else if (typeof(TFeature) == typeof(IConnectionLifetimeFeature))
+ {
+ _currentIConnectionLifetimeFeature = feature;
+ }
+ else
+ {
+ ExtraFeatureSet(typeof(TFeature), feature);
+ }
+ }
+
+ private IEnumerable> FastEnumerable()
+ {
+ if (_currentIConnectionIdFeature != null)
+ {
+ yield return new KeyValuePair(IConnectionIdFeatureType, _currentIConnectionIdFeature);
+ }
+ if (_currentIConnectionTransportFeature != null)
+ {
+ yield return new KeyValuePair(IConnectionTransportFeatureType, _currentIConnectionTransportFeature);
+ }
+ if (_currentIConnectionItemsFeature != null)
+ {
+ yield return new KeyValuePair(IConnectionItemsFeatureType, _currentIConnectionItemsFeature);
+ }
+ if (_currentIMemoryPoolFeature != null)
+ {
+ yield return new KeyValuePair(IMemoryPoolFeatureType, _currentIMemoryPoolFeature);
+ }
+ if (_currentIConnectionLifetimeFeature != null)
+ {
+ yield return new KeyValuePair(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature);
+ }
+
+ if (MaybeExtra != null)
+ {
+ foreach (var item in MaybeExtra)
+ {
+ yield return item;
+ }
+ }
+ }
+
+ IEnumerator> IEnumerable>.GetEnumerator() => FastEnumerable().GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
+ }
+}
diff --git a/Projects/Server/Kestrel/TransportConnection.cs b/Projects/Server/Kestrel/TransportConnection.cs
new file mode 100644
index 000000000..fcd066608
--- /dev/null
+++ b/Projects/Server/Kestrel/TransportConnection.cs
@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Net;
+using System.Threading;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Connections
+{
+ public abstract partial class TransportConnection : ConnectionContext
+ {
+ private IDictionary _items;
+ private string _connectionId;
+
+ public TransportConnection()
+ {
+ FastReset();
+ }
+
+ public override EndPoint LocalEndPoint { get; set; }
+ public override EndPoint RemoteEndPoint { get; set; }
+
+ public override string ConnectionId
+ {
+ get => _connectionId ??= CorrelationIdGenerator.GetNextId();
+ set => _connectionId = value;
+ }
+
+ public override IFeatureCollection Features => this;
+
+ public virtual MemoryPool MemoryPool { get; }
+
+ public override IDuplexPipe Transport { get; set; }
+
+ public IDuplexPipe Application { get; set; }
+
+ public override IDictionary Items
+ {
+ get => _items ??= new ConnectionItems();
+ set => _items = value;
+ }
+
+ public override CancellationToken ConnectionClosed { get; set; }
+
+ // DO NOT remove this override to ConnectionContext.Abort. Doing so would cause
+ // any TransportConnection that does not override Abort or calls base.Abort
+ // to stack overflow when IConnectionLifetimeFeature.Abort() is called.
+ // That said, all derived types should override this method should override
+ // this implementation of Abort because canceling pending output reads is not
+ // sufficient to abort the connection if there is backpressure.
+ public override void Abort(ConnectionAbortedException abortReason)
+ {
+ Application.Input.CancelPendingRead();
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/IAsyncDisposable.cs b/Projects/Server/LibUv/IAsyncDisposable.cs
new file mode 100644
index 000000000..70f4a53a5
--- /dev/null
+++ b/Projects/Server/LibUv/IAsyncDisposable.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Libuv
+{
+ interface IAsyncDisposable
+ {
+ Task DisposeAsync();
+ }
+}
diff --git a/Projects/Server/LibUv/ILibuvTrace.cs b/Projects/Server/LibUv/ILibuvTrace.cs
new file mode 100644
index 000000000..371018cd2
--- /dev/null
+++ b/Projects/Server/LibUv/ILibuvTrace.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ public interface ILibuvTrace : ILogger
+ {
+ void ConnectionRead(string connectionId, int count);
+
+ void ConnectionReadFin(string connectionId);
+
+ void ConnectionWriteFin(string connectionId, string reason);
+
+ void ConnectionWrite(string connectionId, int count);
+
+ void ConnectionWriteCallback(string connectionId, int status);
+
+ void ConnectionError(string connectionId, Exception ex);
+
+ void ConnectionReset(string connectionId);
+
+ void ConnectionPause(string connectionId);
+
+ void ConnectionResume(string connectionId);
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/LibuvFunctions.cs b/Projects/Server/LibUv/Internal/LibuvFunctions.cs
new file mode 100644
index 000000000..f215c0ae6
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/LibuvFunctions.cs
@@ -0,0 +1,612 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Server;
+
+namespace Libuv.Internal
+{
+ public class LibuvFunctions
+ {
+ public LibuvFunctions()
+ {
+ _uv_loop_init = NativeMethods.uv_loop_init;
+ _uv_loop_close = NativeMethods.uv_loop_close;
+ _uv_run = NativeMethods.uv_run;
+ _uv_stop = NativeMethods.uv_stop;
+ _uv_ref = NativeMethods.uv_ref;
+ _uv_unref = NativeMethods.uv_unref;
+ _uv_fileno = NativeMethods.uv_fileno;
+ _uv_close = NativeMethods.uv_close;
+ _uv_async_init = NativeMethods.uv_async_init;
+ _uv_async_send = NativeMethods.uv_async_send;
+ _uv_unsafe_async_send = NativeMethods.uv_unsafe_async_send;
+ _uv_tcp_init = NativeMethods.uv_tcp_init;
+ _uv_tcp_bind = NativeMethods.uv_tcp_bind;
+ _uv_tcp_open = NativeMethods.uv_tcp_open;
+ _uv_tcp_nodelay = NativeMethods.uv_tcp_nodelay;
+ _uv_pipe_init = NativeMethods.uv_pipe_init;
+ _uv_pipe_bind = NativeMethods.uv_pipe_bind;
+ _uv_pipe_open = NativeMethods.uv_pipe_open;
+ _uv_listen = NativeMethods.uv_listen;
+ _uv_accept = NativeMethods.uv_accept;
+ _uv_pipe_connect = NativeMethods.uv_pipe_connect;
+ _uv_pipe_pending_count = NativeMethods.uv_pipe_pending_count;
+ _uv_read_start = NativeMethods.uv_read_start;
+ _uv_read_stop = NativeMethods.uv_read_stop;
+ _uv_try_write = NativeMethods.uv_try_write;
+ unsafe
+ {
+ _uv_write = NativeMethods.uv_write;
+ _uv_write2 = NativeMethods.uv_write2;
+ }
+ _uv_err_name = NativeMethods.uv_err_name;
+ _uv_strerror = NativeMethods.uv_strerror;
+ _uv_loop_size = NativeMethods.uv_loop_size;
+ _uv_handle_size = NativeMethods.uv_handle_size;
+ _uv_req_size = NativeMethods.uv_req_size;
+ _uv_ip4_addr = NativeMethods.uv_ip4_addr;
+ _uv_ip6_addr = NativeMethods.uv_ip6_addr;
+ _uv_tcp_getpeername = NativeMethods.uv_tcp_getpeername;
+ _uv_tcp_getsockname = NativeMethods.uv_tcp_getsockname;
+ _uv_walk = NativeMethods.uv_walk;
+ _uv_timer_init = NativeMethods.uv_timer_init;
+ _uv_timer_start = NativeMethods.uv_timer_start;
+ _uv_timer_stop = NativeMethods.uv_timer_stop;
+ _uv_now = NativeMethods.uv_now;
+ }
+
+ // Second ctor that doesn't set any fields only to be used by MockLibuv
+ public LibuvFunctions(bool onlyForTesting)
+ {
+ }
+
+ public void ThrowIfErrored(int statusCode)
+ {
+ // Note: method is explicitly small so the success case is easily inlined
+ if (statusCode < 0) ThrowError(statusCode);
+ }
+
+ private void ThrowError(int statusCode)
+ {
+ // Note: only has one throw block so it will marked as "Does not return" by the jit
+ // and not inlined into previous function, while also marking as a function
+ // that does not need cpu register prep to call (see: https://github.com/dotnet/coreclr/pull/6103)
+ throw GetError(statusCode);
+ }
+
+ public void Check(int statusCode, out UvException error)
+ {
+ // Note: method is explicitly small so the success case is easily inlined
+ error = statusCode < 0 ? GetError(statusCode) : null;
+ }
+
+ // Note: method marked as NoInlining so it doesn't bloat either of the two preceding functions
+ // Check and ThrowError and alter their jit heuristics.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private UvException GetError(int statusCode) =>
+ new UvException($"Error {statusCode} {err_name(statusCode)} {strerror(statusCode)}", statusCode);
+
+ public Func _uv_loop_init;
+ public void loop_init(UvLoopHandle handle)
+ {
+ ThrowIfErrored(_uv_loop_init(handle));
+ }
+
+ public Func _uv_loop_close;
+ public void loop_close(UvLoopHandle handle)
+ {
+ handle.Validate(true);
+ ThrowIfErrored(_uv_loop_close(handle.InternalGetHandle()));
+ }
+
+ public Func _uv_run;
+ public void run(UvLoopHandle handle, int mode)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_run(handle, mode));
+ }
+
+ public Action _uv_stop;
+ public void stop(UvLoopHandle handle)
+ {
+ handle.Validate();
+ _uv_stop(handle);
+ }
+
+ public Action _uv_ref;
+ public void @ref(UvHandle handle)
+ {
+ handle.Validate();
+ _uv_ref(handle);
+ }
+
+ public Action _uv_unref;
+ public void unref(UvHandle handle)
+ {
+ handle.Validate();
+ _uv_unref(handle);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate int uv_fileno_func(UvHandle handle, ref IntPtr socket);
+ public uv_fileno_func _uv_fileno;
+ public void uv_fileno(UvHandle handle, ref IntPtr socket)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_fileno(handle, ref socket));
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_close_cb(IntPtr handle);
+ public Action _uv_close;
+ public void close(UvHandle handle, uv_close_cb close_cb)
+ {
+ handle.Validate(true);
+ _uv_close(handle.InternalGetHandle(), close_cb);
+ }
+
+ public void close(IntPtr handle, uv_close_cb close_cb)
+ {
+ _uv_close(handle, close_cb);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_async_cb(IntPtr handle);
+ public Func _uv_async_init;
+ public void async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb)
+ {
+ loop.Validate();
+ handle.Validate();
+ ThrowIfErrored(_uv_async_init(loop, handle, cb));
+ }
+
+ public Func _uv_async_send;
+ public void async_send(UvAsyncHandle handle)
+ {
+ ThrowIfErrored(_uv_async_send(handle));
+ }
+
+ public Func _uv_unsafe_async_send;
+ public void unsafe_async_send(IntPtr handle)
+ {
+ ThrowIfErrored(_uv_unsafe_async_send(handle));
+ }
+
+ public Func _uv_tcp_init;
+ public void tcp_init(UvLoopHandle loop, UvTcpHandle handle)
+ {
+ loop.Validate();
+ handle.Validate();
+ ThrowIfErrored(_uv_tcp_init(loop, handle));
+ }
+
+ public delegate int uv_tcp_bind_func(UvTcpHandle handle, ref SockAddr addr, int flags);
+ public uv_tcp_bind_func _uv_tcp_bind;
+ public void tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_tcp_bind(handle, ref addr, flags));
+ }
+
+ public Func _uv_tcp_open;
+ public void tcp_open(UvTcpHandle handle, IntPtr hSocket)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_tcp_open(handle, hSocket));
+ }
+
+ public Func _uv_tcp_nodelay;
+ public void tcp_nodelay(UvTcpHandle handle, bool enable)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_tcp_nodelay(handle, enable ? 1 : 0));
+ }
+
+ public Func _uv_pipe_init;
+ public void pipe_init(UvLoopHandle loop, UvPipeHandle handle, bool ipc)
+ {
+ loop.Validate();
+ handle.Validate();
+ ThrowIfErrored(_uv_pipe_init(loop, handle, ipc ? -1 : 0));
+ }
+
+ public Func _uv_pipe_bind;
+ public void pipe_bind(UvPipeHandle handle, string name)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_pipe_bind(handle, name));
+ }
+
+ public Func _uv_pipe_open;
+ public void pipe_open(UvPipeHandle handle, IntPtr hSocket)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_pipe_open(handle, hSocket));
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_connection_cb(IntPtr server, int status);
+ public Func _uv_listen;
+ public void listen(UvStreamHandle handle, int backlog, uv_connection_cb cb)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_listen(handle, backlog, cb));
+ }
+
+ public Func _uv_accept;
+ public void accept(UvStreamHandle server, UvStreamHandle client)
+ {
+ server.Validate();
+ client.Validate();
+ ThrowIfErrored(_uv_accept(server, client));
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_connect_cb(IntPtr req, int status);
+ public Action _uv_pipe_connect;
+ public void pipe_connect(UvConnectRequest req, UvPipeHandle handle, string name, uv_connect_cb cb)
+ {
+ req.Validate();
+ handle.Validate();
+ _uv_pipe_connect(req, handle, name, cb);
+ }
+
+ public Func _uv_pipe_pending_count;
+ public int pipe_pending_count(UvPipeHandle handle)
+ {
+ handle.Validate();
+ return _uv_pipe_pending_count(handle);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_alloc_cb(IntPtr server, int suggested_size, out uv_buf_t buf);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_read_cb(IntPtr server, int nread, ref uv_buf_t buf);
+ public Func _uv_read_start;
+ public void read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_read_start(handle, alloc_cb, read_cb));
+ }
+
+ public Func _uv_read_stop;
+ public void read_stop(UvStreamHandle handle)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_read_stop(handle));
+ }
+
+ public Func _uv_try_write;
+ public int try_write(UvStreamHandle handle, uv_buf_t[] bufs, int nbufs)
+ {
+ handle.Validate();
+ var count = _uv_try_write(handle, bufs, nbufs);
+ ThrowIfErrored(count);
+ return count;
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_write_cb(IntPtr req, int status);
+
+ public unsafe delegate int uv_write_func(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb);
+ public uv_write_func _uv_write;
+ public unsafe void write(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb)
+ {
+ req.Validate();
+ handle.Validate();
+ ThrowIfErrored(_uv_write(req, handle, bufs, nbufs, cb));
+ }
+
+ public unsafe delegate int uv_write2_func(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, UvStreamHandle sendHandle, uv_write_cb cb);
+ public uv_write2_func _uv_write2;
+ public unsafe void write2(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, UvStreamHandle sendHandle, uv_write_cb cb)
+ {
+ req.Validate();
+ handle.Validate();
+ ThrowIfErrored(_uv_write2(req, handle, bufs, nbufs, sendHandle, cb));
+ }
+
+ public Func _uv_err_name;
+ public string err_name(int err)
+ {
+ IntPtr ptr = _uv_err_name(err);
+ return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr);
+ }
+
+ public Func _uv_strerror;
+ public string strerror(int err)
+ {
+ IntPtr ptr = _uv_strerror(err);
+ return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr);
+ }
+
+ public Func _uv_loop_size;
+ public int loop_size() => _uv_loop_size();
+
+ public Func _uv_handle_size;
+ public int handle_size(HandleType handleType) => _uv_handle_size(handleType);
+
+ public Func _uv_req_size;
+ public int req_size(RequestType reqType) => _uv_req_size(reqType);
+
+ public delegate int uv_ip4_addr_func(string ip, int port, out SockAddr addr);
+ public uv_ip4_addr_func _uv_ip4_addr;
+ public void ip4_addr(string ip, int port, out SockAddr addr, out UvException error)
+ {
+ Check(_uv_ip4_addr(ip, port, out addr), out error);
+ }
+
+ public delegate int uv_ip6_addr_func(string ip, int port, out SockAddr addr);
+ public uv_ip6_addr_func _uv_ip6_addr;
+ public void ip6_addr(string ip, int port, out SockAddr addr, out UvException error)
+ {
+ Check(_uv_ip6_addr(ip, port, out addr), out error);
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_walk_cb(IntPtr handle, IntPtr arg);
+ public Func _uv_walk;
+ public void walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg)
+ {
+ loop.Validate();
+ _uv_walk(loop, walk_cb, arg);
+ }
+
+ public Func _uv_timer_init;
+ public void timer_init(UvLoopHandle loop, UvTimerHandle handle)
+ {
+ loop.Validate();
+ handle.Validate();
+ ThrowIfErrored(_uv_timer_init(loop, handle));
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void uv_timer_cb(IntPtr handle);
+ public Func _uv_timer_start;
+ public void timer_start(UvTimerHandle handle, uv_timer_cb cb, long timeout, long repeat)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_timer_start(handle, cb, timeout, repeat));
+ }
+
+ public Func _uv_timer_stop;
+ public void timer_stop(UvTimerHandle handle)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_timer_stop(handle));
+ }
+
+ public Func _uv_now;
+ public long now(UvLoopHandle loop)
+ {
+ loop.Validate();
+ return _uv_now(loop);
+ }
+
+ public delegate int uv_tcp_getsockname_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
+ public uv_tcp_getsockname_func _uv_tcp_getsockname;
+ public void tcp_getsockname(UvTcpHandle handle, out SockAddr addr, ref int namelen)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_tcp_getsockname(handle, out addr, ref namelen));
+ }
+
+ public delegate int uv_tcp_getpeername_func(UvTcpHandle handle, out SockAddr addr, ref int namelen);
+ public uv_tcp_getpeername_func _uv_tcp_getpeername;
+ public void tcp_getpeername(UvTcpHandle handle, out SockAddr addr, ref int namelen)
+ {
+ handle.Validate();
+ ThrowIfErrored(_uv_tcp_getpeername(handle, out addr, ref namelen));
+ }
+
+ public uv_buf_t buf_init(IntPtr memory, int len) => new uv_buf_t(memory, len, Core.IsWindows);
+
+ public struct uv_buf_t
+ {
+ // this type represents a WSABUF struct on Windows
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms741542(v=vs.85).aspx
+ // and an iovec struct on *nix
+ // http://man7.org/linux/man-pages/man2/readv.2.html
+ // because the order of the fields in these structs is different, the field
+ // names in this type don't have meaningful symbolic names. instead, they are
+ // assigned in the correct order by the constructor at runtime
+
+ private readonly IntPtr _field0;
+ private readonly IntPtr _field1;
+
+ public uv_buf_t(IntPtr memory, int len, bool windows)
+ {
+ if (windows)
+ {
+ _field0 = (IntPtr)len;
+ _field1 = memory;
+ }
+ else
+ {
+ _field0 = memory;
+ _field1 = (IntPtr)len;
+ }
+ }
+ }
+
+ public enum HandleType
+ {
+ Unknown = 0,
+ ASYNC,
+ CHECK,
+ FS_EVENT,
+ FS_POLL,
+ HANDLE,
+ IDLE,
+ NAMED_PIPE,
+ POLL,
+ PREPARE,
+ PROCESS,
+ STREAM,
+ TCP,
+ TIMER,
+ TTY,
+ UDP,
+ SIGNAL,
+ }
+
+ public enum RequestType
+ {
+ Unknown = 0,
+ REQ,
+ CONNECT,
+ WRITE,
+ SHUTDOWN,
+ UDP_SEND,
+ FS,
+ WORK,
+ GETADDRINFO,
+ GETNAMEINFO,
+ }
+
+ private static class NativeMethods
+ {
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_loop_init(UvLoopHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_loop_close(IntPtr a0);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_run(UvLoopHandle handle, int mode);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void uv_stop(UvLoopHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void uv_ref(UvHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void uv_unref(UvHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_fileno(UvHandle handle, ref IntPtr socket);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern void uv_close(IntPtr handle, uv_close_cb close_cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_async_send(UvAsyncHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl, EntryPoint = "uv_async_send")]
+ public static extern int uv_unsafe_async_send(IntPtr handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_open(UvTcpHandle handle, IntPtr hSocket);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_nodelay(UvTcpHandle handle, int enable);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_pipe_init(UvLoopHandle loop, UvPipeHandle handle, int ipc);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_pipe_bind(UvPipeHandle loop, string name);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_pipe_open(UvPipeHandle handle, IntPtr hSocket);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_listen(UvStreamHandle handle, int backlog, uv_connection_cb cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_accept(UvStreamHandle server, UvStreamHandle client);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern void uv_pipe_connect(UvConnectRequest req, UvPipeHandle handle, string name, uv_connect_cb cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_pipe_pending_count(UvPipeHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_read_stop(UvStreamHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_try_write(UvStreamHandle handle, uv_buf_t[] bufs, int nbufs);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern unsafe int uv_write(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern unsafe int uv_write2(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, UvStreamHandle sendHandle, uv_write_cb cb);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr uv_err_name(int err);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr uv_strerror(int err);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_loop_size();
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_handle_size(HandleType handleType);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_req_size(RequestType reqType);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_ip4_addr(string ip, int port, out SockAddr addr);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_ip6_addr(string ip, int port, out SockAddr addr);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_getsockname(UvTcpHandle handle, out SockAddr name, ref int namelen);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_tcp_getpeername(UvTcpHandle handle, out SockAddr name, ref int namelen);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_timer_init(UvLoopHandle loop, UvTimerHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_timer_start(UvTimerHandle handle, uv_timer_cb cb, long timeout, long repeat);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern int uv_timer_stop(UvTimerHandle handle);
+
+ [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
+ public static extern long uv_now(UvLoopHandle loop);
+
+ [DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern unsafe int WSAIoctl(
+ IntPtr socket,
+ int dwIoControlCode,
+ int* lpvInBuffer,
+ uint cbInBuffer,
+ int* lpvOutBuffer,
+ int cbOutBuffer,
+ out uint lpcbBytesReturned,
+ IntPtr lpOverlapped,
+ IntPtr lpCompletionRoutine
+ );
+
+ [DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)]
+ public static extern int WSAGetLastError();
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/SockAddr.cs b/Projects/Server/LibUv/Internal/SockAddr.cs
new file mode 100644
index 000000000..09c6e6473
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/SockAddr.cs
@@ -0,0 +1,107 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net;
+using System.Runtime.InteropServices;
+using Server;
+
+namespace Libuv.Internal
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SockAddr
+ {
+ // this type represents native memory occupied by sockaddr struct
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms740496(v=vs.85).aspx
+ // although the c/c++ header defines it as a 2-byte short followed by a 14-byte array,
+ // the simplest way to reserve the same size in c# is with four nameless long values
+ private long _field0;
+ private long _field1;
+ private long _field2;
+ private long _field3;
+
+ public SockAddr(long ignored) => _field0 = _field1 = _field2 = _field3 = 0;
+
+ public unsafe IPEndPoint GetIPEndPoint()
+ {
+ // The bytes are represented in network byte order.
+ //
+ // Example 1: [2001:4898:e0:391:b9ef:1124:9d3e:a354]:39179
+ //
+ // 0000 0000 0b99 0017 => The third and fourth bytes 990B is the actual port
+ // 9103 e000 9848 0120 => IPv6 address is represented in the 128bit field1 and field2.
+ // 54a3 3e9d 2411 efb9 Read these two 64-bit long from right to left byte by byte.
+ // 0000 0000 0000 0010 => Scope ID 0x10 (eg [::1%16]) the first 4 bytes of field3 in host byte order.
+ //
+ // Example 2: 10.135.34.141:39178 when adopt dual-stack sockets, IPv4 is mapped to IPv6
+ //
+ // 0000 0000 0a99 0017 => The port representation are the same
+ // 0000 0000 0000 0000
+ // 8d22 870a ffff 0000 => IPv4 occupies the last 32 bit: 0A.87.22.8d is the actual address.
+ // 0000 0000 0000 0000
+ //
+ // Example 3: 10.135.34.141:12804, not dual-stack sockets
+ //
+ // 8d22 870a fd31 0002 => sa_family == AF_INET (02)
+ // 0000 0000 0000 0000
+ // 0000 0000 0000 0000
+ // 0000 0000 0000 0000
+ //
+ // Example 4: 127.0.0.1:52798, on a Mac OS
+ //
+ // 0100 007F 3ECE 0210 => sa_family == AF_INET (02) Note that struct sockaddr on mac use
+ // 0000 0000 0000 0000 the second unint8 field for sa family type
+ // 0000 0000 0000 0000 http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/socket.h
+ // 0000 0000 0000 0000
+ //
+ // Reference:
+ // - Windows: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx
+ // - Linux: https://github.com/torvalds/linux/blob/6a13feb9c82803e2b815eca72fa7a9f5561d7861/include/linux/socket.h
+ // - Linux (sin6_scope_id): https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/net/sunrpc/addr.c#L82
+ // - Apple: http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/bsd/sys/socket.h
+
+ // Quick calculate the port by mask the field and locate the byte 3 and byte 4
+ // and then shift them to correct place to form a int.
+ var port = ((int)(_field0 & 0x00FF0000) >> 8) | (int)((_field0 & 0xFF000000) >> 24);
+
+ int family = (int)_field0;
+ if (Core.IsDarwin)
+ // see explanation in example 4
+ family >>= 8;
+ family &= 0xFF;
+
+ if (family == 2)
+ // AF_INET => IPv4
+ return new IPEndPoint(new IPAddress((_field0 >> 32) & 0xFFFFFFFF), port);
+
+ if (IsIPv4MappedToIPv6())
+ {
+ var ipv4bits = (_field2 >> 32) & 0x00000000FFFFFFFF;
+ return new IPEndPoint(new IPAddress(ipv4bits), port);
+ }
+
+ // otherwise IPv6
+ var bytes = new byte[16];
+ fixed (byte* b = bytes)
+ {
+ *(long*)b = _field1;
+ *(long*)(b + 8) = _field2;
+ }
+
+ return new IPEndPoint(new IPAddress(bytes, ScopeId), port);
+ }
+
+ public uint ScopeId
+ {
+ get => (uint)_field3;
+ set
+ {
+ _field3 &= unchecked ((long)0xFFFFFFFF00000000);
+ _field3 |= value;
+ }
+ }
+
+ // If the IPAddress is an IPv4 mapped to IPv6, return the IPv4 representation instead.
+ // For example [::FFFF:127.0.0.1] will be transform to IPAddress of 127.0.0.1
+ private bool IsIPv4MappedToIPv6() => _field1 == 0 && (_field2 & 0xFFFFFFFF) == 0xFFFF0000;
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvAsyncHandle.cs b/Projects/Server/LibUv/Internal/UvAsyncHandle.cs
new file mode 100644
index 000000000..f27a179a8
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvAsyncHandle.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Libuv.Internal
+{
+ public class UvAsyncHandle : UvHandle
+ {
+ private static readonly LibuvFunctions.uv_close_cb _destroyMemory = handle => DestroyMemory(handle);
+
+ private static readonly LibuvFunctions.uv_async_cb _uv_async_cb = handle => AsyncCb(handle);
+ private Action _callback;
+ private Action, IntPtr> _queueCloseHandle;
+
+ public UvAsyncHandle(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ public void Init(UvLoopHandle loop, Action callback, Action, IntPtr> queueCloseHandle)
+ {
+ CreateMemory(
+ loop.Libuv,
+ loop.ThreadId,
+ loop.Libuv.handle_size(LibuvFunctions.HandleType.ASYNC));
+
+ _callback = callback;
+ _queueCloseHandle = queueCloseHandle;
+ _uv.async_init(loop, this, _uv_async_cb);
+ }
+
+ public void Send()
+ {
+ _uv.async_send(this);
+ }
+
+ private static void AsyncCb(IntPtr handle)
+ {
+ FromIntPtr(handle)._callback.Invoke();
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ var memory = handle;
+ if (memory != IntPtr.Zero)
+ {
+ handle = IntPtr.Zero;
+
+ if (Thread.CurrentThread.ManagedThreadId == ThreadId)
+ {
+ _uv.close(memory, _destroyMemory);
+ }
+ else if (_queueCloseHandle != null)
+ {
+ // This can be called from the finalizer.
+ // Ensure the closure doesn't reference "this".
+ LibuvFunctions uv = _uv;
+ _queueCloseHandle(memory2 => uv.close(memory2, _destroyMemory), memory);
+ uv.unsafe_async_send(memory);
+ }
+ else
+ {
+ Debug.Assert(false, "UvAsyncHandle not initialized with queueCloseHandle action");
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvConnectRequest.cs b/Projects/Server/LibUv/Internal/UvConnectRequest.cs
new file mode 100644
index 000000000..cc60539ac
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvConnectRequest.cs
@@ -0,0 +1,75 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv.Internal
+{
+ ///
+ /// Summary description for UvWriteRequest
+ ///
+ public class UvConnectRequest : UvRequest
+ {
+ private static readonly LibuvFunctions.uv_connect_cb _uv_connect_cb = UvConnectCb;
+
+ private Action _callback;
+ private object _state;
+
+ public UvConnectRequest(ILibuvTrace logger) : base (logger)
+ {
+ }
+
+ public override void Init(LibuvThread thread)
+ {
+ DangerousInit(thread.Loop);
+
+ base.Init(thread);
+ }
+
+ public void DangerousInit(UvLoopHandle loop)
+ {
+ var requestSize = loop.Libuv.req_size(LibuvFunctions.RequestType.CONNECT);
+ CreateMemory(
+ loop.Libuv,
+ loop.ThreadId,
+ requestSize);
+ }
+
+ public void Connect(
+ UvPipeHandle pipe,
+ string name,
+ Action callback,
+ object state)
+ {
+ _callback = callback;
+ _state = state;
+
+ Libuv.pipe_connect(this, pipe, name, _uv_connect_cb);
+ }
+
+ private static void UvConnectCb(IntPtr ptr, int status)
+ {
+ var req = FromIntPtr(ptr);
+
+ var callback = req._callback;
+ req._callback = null;
+
+ var state = req._state;
+ req._state = null;
+
+ UvException error = null;
+ if (status < 0) req.Libuv.Check(status, out error);
+
+ try
+ {
+ callback(req, status, error, state);
+ }
+ catch (Exception ex)
+ {
+ req._log.LogError(0, ex, "UvConnectRequest");
+ throw;
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvException.cs b/Projects/Server/LibUv/Internal/UvException.cs
new file mode 100644
index 000000000..3d4e492ef
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvException.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Libuv.Internal
+{
+ public class UvException : Exception
+ {
+ public UvException(string message, int statusCode) : base(message) => StatusCode = statusCode;
+
+ public int StatusCode { get; }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvHandle.cs b/Projects/Server/LibUv/Internal/UvHandle.cs
new file mode 100644
index 000000000..888791bcc
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvHandle.cs
@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Libuv.Internal
+{
+ public abstract class UvHandle : UvMemory
+ {
+ private static readonly LibuvFunctions.uv_close_cb _destroyMemory = DestroyMemory;
+ private Action, IntPtr> _queueCloseHandle;
+
+ protected UvHandle(ILibuvTrace logger) : base (logger)
+ {
+ }
+
+ protected void CreateHandle(
+ LibuvFunctions uv,
+ int threadId,
+ int size,
+ Action, IntPtr> queueCloseHandle)
+ {
+ _queueCloseHandle = queueCloseHandle;
+ CreateMemory(uv, threadId, size);
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ IntPtr memory = handle;
+ if (memory != IntPtr.Zero)
+ {
+ handle = IntPtr.Zero;
+
+ if (Thread.CurrentThread.ManagedThreadId == ThreadId)
+ {
+ _uv.close(memory, _destroyMemory);
+ }
+ else if (_queueCloseHandle != null)
+ {
+ // This can be called from the finalizer.
+ // Ensure the closure doesn't reference "this".
+ LibuvFunctions uv = _uv;
+ _queueCloseHandle(memory2 => uv.close(memory2, _destroyMemory), memory);
+ }
+ else
+ {
+ Debug.Assert(false, "UvHandle not initialized with queueCloseHandle action");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void Reference()
+ {
+ _uv.@ref(this);
+ }
+
+ public void Unreference()
+ {
+ _uv.unref(this);
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvLoopHandle.cs b/Projects/Server/LibUv/Internal/UvLoopHandle.cs
new file mode 100644
index 000000000..c99c8896f
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvLoopHandle.cs
@@ -0,0 +1,54 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+
+namespace Libuv.Internal
+{
+ public class UvLoopHandle : UvMemory
+ {
+ public UvLoopHandle(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ public void Init(LibuvFunctions uv)
+ {
+ CreateMemory(
+ uv,
+ Thread.CurrentThread.ManagedThreadId,
+ uv.loop_size());
+
+ _uv.loop_init(this);
+ }
+
+ public void Run(int mode = 0)
+ {
+ _uv.run(this, mode);
+ }
+
+ public void Stop()
+ {
+ _uv.stop(this);
+ }
+
+ public long Now() => _uv.now(this);
+
+ protected override unsafe bool ReleaseHandle()
+ {
+ var memory = handle;
+ if (memory != IntPtr.Zero)
+ {
+ // loop_close clears the gcHandlePtr
+ var gcHandlePtr = *(IntPtr*)memory;
+
+ _uv.loop_close(this);
+ handle = IntPtr.Zero;
+
+ DestroyMemory(memory, gcHandlePtr);
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvMemory.cs b/Projects/Server/LibUv/Internal/UvMemory.cs
new file mode 100644
index 000000000..b5369ab5c
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvMemory.cs
@@ -0,0 +1,79 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+#define TRACE
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Libuv.Internal
+{
+ ///
+ /// Summary description for UvMemory
+ ///
+ public abstract class UvMemory : SafeHandle
+ {
+ protected LibuvFunctions _uv;
+ protected int _threadId;
+ protected readonly ILibuvTrace _log;
+ private readonly GCHandleType _handleType;
+
+ protected UvMemory(ILibuvTrace logger, GCHandleType handleType = GCHandleType.Weak) : base(IntPtr.Zero, true)
+ {
+ _log = logger;
+ _handleType = handleType;
+ }
+
+ public LibuvFunctions Libuv => _uv;
+
+ public override bool IsInvalid => handle == IntPtr.Zero;
+
+ public int ThreadId
+ {
+ get => _threadId;
+ private set => _threadId = value;
+ }
+
+ protected unsafe void CreateMemory(LibuvFunctions uv, int threadId, int size)
+ {
+ _uv = uv;
+ ThreadId = threadId;
+
+ handle = Marshal.AllocCoTaskMem(size);
+ *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this, _handleType));
+ }
+
+ protected static unsafe void DestroyMemory(IntPtr memory)
+ {
+ var gcHandlePtr = *(IntPtr*)memory;
+ DestroyMemory(memory, gcHandlePtr);
+ }
+
+ protected static void DestroyMemory(IntPtr memory, IntPtr gcHandlePtr)
+ {
+ if (gcHandlePtr != IntPtr.Zero)
+ {
+ var gcHandle = GCHandle.FromIntPtr(gcHandlePtr);
+ gcHandle.Free();
+ }
+ Marshal.FreeCoTaskMem(memory);
+ }
+
+ public IntPtr InternalGetHandle() => handle;
+
+ public void Validate(bool closed = false)
+ {
+ Debug.Assert(closed || !IsClosed, "Handle is closed");
+ Debug.Assert(!IsInvalid, "Handle is invalid");
+
+ Debug.Assert(_threadId == Thread.CurrentThread.ManagedThreadId, "ThreadId is incorrect");
+ }
+
+ public static unsafe THandle FromIntPtr(IntPtr handle)
+ {
+ GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle);
+ return (THandle)gcHandle.Target;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvPipeHandle.cs b/Projects/Server/LibUv/Internal/UvPipeHandle.cs
new file mode 100644
index 000000000..188777209
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvPipeHandle.cs
@@ -0,0 +1,36 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Libuv.Internal
+{
+ public class UvPipeHandle : UvStreamHandle
+ {
+ public UvPipeHandle(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ public void Init(UvLoopHandle loop, Action, IntPtr> queueCloseHandle, bool ipc = false)
+ {
+ CreateHandle(
+ loop.Libuv,
+ loop.ThreadId,
+ loop.Libuv.handle_size(LibuvFunctions.HandleType.NAMED_PIPE), queueCloseHandle);
+
+ _uv.pipe_init(loop, this, ipc);
+ }
+
+ public void Open(IntPtr fileDescriptor)
+ {
+ _uv.pipe_open(this, fileDescriptor);
+ }
+
+ public void Bind(string name)
+ {
+ _uv.pipe_bind(this, name);
+ }
+
+ public int PendingCount() => _uv.pipe_pending_count(this);
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvRequest.cs b/Projects/Server/LibUv/Internal/UvRequest.cs
new file mode 100644
index 000000000..ed7199ce2
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvRequest.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Libuv.Internal
+{
+ public class UvRequest : UvMemory
+ {
+ protected UvRequest(ILibuvTrace logger) : base(logger, GCHandleType.Normal)
+ {
+ }
+
+ public virtual void Init(LibuvThread thread)
+ {
+#if DEBUG
+ // Store weak handles to all UvRequest objects so we can do leak detection
+ // while running tests
+ thread.Requests.Add(new WeakReference(this));
+#endif
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ DestroyMemory(handle);
+ handle = IntPtr.Zero;
+ return true;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvStreamHandle.cs b/Projects/Server/LibUv/Internal/UvStreamHandle.cs
new file mode 100644
index 000000000..2edf8253b
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvStreamHandle.cs
@@ -0,0 +1,151 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv.Internal
+{
+ public abstract class UvStreamHandle : UvHandle
+ {
+ private static readonly LibuvFunctions.uv_connection_cb _uv_connection_cb = UvConnectionCb;
+ // Ref and out lamda params must be explicitly typed
+ private static readonly LibuvFunctions.uv_alloc_cb _uv_alloc_cb =
+ (IntPtr handle, int suggested_size, out LibuvFunctions.uv_buf_t buf) => UvAllocCb(handle, suggested_size, out buf);
+ private static readonly LibuvFunctions.uv_read_cb _uv_read_cb =
+ (IntPtr handle, int status, ref LibuvFunctions.uv_buf_t buf) => UvReadCb(handle, status, ref buf);
+
+ private Action _listenCallback;
+ private object _listenState;
+ private GCHandle _listenVitality;
+
+ private Func _allocCallback;
+ private Action _readCallback;
+ private object _readState;
+ private GCHandle _readVitality;
+
+ protected UvStreamHandle(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ if (_listenVitality.IsAllocated) _listenVitality.Free();
+ if (_readVitality.IsAllocated) _readVitality.Free();
+ return base.ReleaseHandle();
+ }
+
+ public void Listen(int backlog, Action callback, object state)
+ {
+ if (_listenVitality.IsAllocated) throw new InvalidOperationException("TODO: Listen may not be called more than once");
+ try
+ {
+ _listenCallback = callback;
+ _listenState = state;
+ _listenVitality = GCHandle.Alloc(this, GCHandleType.Normal);
+ _uv.listen(this, backlog, _uv_connection_cb);
+ }
+ catch
+ {
+ _listenCallback = null;
+ _listenState = null;
+ if (_listenVitality.IsAllocated) _listenVitality.Free();
+ throw;
+ }
+ }
+
+ public void Accept(UvStreamHandle handle)
+ {
+ _uv.accept(this, handle);
+ }
+
+ public void ReadStart(
+ Func allocCallback,
+ Action readCallback,
+ object state)
+ {
+ if (_readVitality.IsAllocated) throw new InvalidOperationException("TODO: ReadStop must be called before ReadStart may be called again");
+
+ try
+ {
+ _allocCallback = allocCallback;
+ _readCallback = readCallback;
+ _readState = state;
+ _readVitality = GCHandle.Alloc(this, GCHandleType.Normal);
+ _uv.read_start(this, _uv_alloc_cb, _uv_read_cb);
+ }
+ catch
+ {
+ _allocCallback = null;
+ _readCallback = null;
+ _readState = null;
+ if (_readVitality.IsAllocated) _readVitality.Free();
+ throw;
+ }
+ }
+
+ // UvStreamHandle.ReadStop() should be idempotent to match uv_read_stop()
+ public void ReadStop()
+ {
+ if (_readVitality.IsAllocated) _readVitality.Free();
+ _allocCallback = null;
+ _readCallback = null;
+ _readState = null;
+ _uv.read_stop(this);
+ }
+
+ public int TryWrite(LibuvFunctions.uv_buf_t buf)
+ {
+ return _uv.try_write(this, new[] { buf }, 1);
+ }
+
+ private static void UvConnectionCb(IntPtr handle, int status)
+ {
+ var stream = FromIntPtr(handle);
+
+ stream.Libuv.Check(status, out var error);
+
+ try
+ {
+ stream._listenCallback(stream, status, error, stream._listenState);
+ }
+ catch (Exception ex)
+ {
+ stream._log.LogError(0, ex, "UvConnectionCb");
+ throw;
+ }
+ }
+
+ private static void UvAllocCb(IntPtr handle, int suggested_size, out LibuvFunctions.uv_buf_t buf)
+ {
+ var stream = FromIntPtr(handle);
+ try
+ {
+ buf = stream._allocCallback(stream, suggested_size, stream._readState);
+ }
+ catch (Exception ex)
+ {
+ stream._log.LogError(0, ex, "UvAllocCb");
+ buf = stream.Libuv.buf_init(IntPtr.Zero, 0);
+ throw;
+ }
+ }
+
+ private static void UvReadCb(IntPtr handle, int status, ref LibuvFunctions.uv_buf_t buf)
+ {
+ var stream = FromIntPtr(handle);
+
+ try
+ {
+ stream._readCallback(stream, status, stream._readState);
+ }
+ catch (Exception ex)
+ {
+ stream._log.LogError(0, ex, "UbReadCb");
+ throw;
+ }
+ }
+
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvTcpHandle.cs b/Projects/Server/LibUv/Internal/UvTcpHandle.cs
new file mode 100644
index 000000000..745477201
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvTcpHandle.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+using System.Runtime.InteropServices;
+
+namespace Libuv.Internal
+{
+ public class UvTcpHandle : UvStreamHandle
+ {
+ public UvTcpHandle(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ public void Init(UvLoopHandle loop, Action, IntPtr> queueCloseHandle)
+ {
+ CreateHandle(
+ loop.Libuv,
+ loop.ThreadId,
+ loop.Libuv.handle_size(LibuvFunctions.HandleType.TCP), queueCloseHandle);
+
+ _uv.tcp_init(loop, this);
+ }
+
+ public void Open(IntPtr fileDescriptor)
+ {
+ _uv.tcp_open(this, fileDescriptor);
+ }
+
+ public void Bind(IPEndPoint endPoint)
+ {
+ var addressText = endPoint.Address.ToString();
+
+ _uv.ip4_addr(addressText, endPoint.Port, out var addr, out var error1);
+
+ if (error1 != null)
+ {
+ _uv.ip6_addr(addressText, endPoint.Port, out addr, out var error2);
+ if (error2 != null) throw error1;
+
+ if (endPoint.Address.ScopeId != addr.ScopeId)
+ // IPAddress.ScopeId cannot be less than 0 or greater than 0xFFFFFFFF
+ // https://msdn.microsoft.com/en-us/library/system.net.ipaddress.scopeid(v=vs.110).aspx
+ addr.ScopeId = (uint)endPoint.Address.ScopeId;
+ }
+
+ _uv.tcp_bind(this, ref addr, 0);
+ }
+
+ public IPEndPoint GetPeerIPEndPoint()
+ {
+ int namelen = Marshal.SizeOf();
+ _uv.tcp_getpeername(this, out var socketAddress, ref namelen);
+
+ return socketAddress.GetIPEndPoint();
+ }
+
+ public IPEndPoint GetSockIPEndPoint()
+ {
+ int namelen = Marshal.SizeOf();
+ _uv.tcp_getsockname(this, out var socketAddress, ref namelen);
+
+ return socketAddress.GetIPEndPoint();
+ }
+
+ public void NoDelay(bool enable)
+ {
+ _uv.tcp_nodelay(this, enable);
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvTimerHandle.cs b/Projects/Server/LibUv/Internal/UvTimerHandle.cs
new file mode 100644
index 000000000..5fc52af17
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvTimerHandle.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv.Internal
+{
+ public class UvTimerHandle : UvHandle
+ {
+ private static readonly LibuvFunctions.uv_timer_cb _uv_timer_cb = UvTimerCb;
+
+ private Action _callback;
+
+ public UvTimerHandle(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ public void Init(UvLoopHandle loop, Action, IntPtr> queueCloseHandle)
+ {
+ CreateHandle(
+ loop.Libuv,
+ loop.ThreadId,
+ loop.Libuv.handle_size(LibuvFunctions.HandleType.TIMER),
+ queueCloseHandle);
+
+ _uv.timer_init(loop, this);
+ }
+
+ public void Start(Action callback, long timeout, long repeat)
+ {
+ _callback = callback;
+ _uv.timer_start(this, _uv_timer_cb, timeout, repeat);
+ }
+
+ public void Stop()
+ {
+ _uv.timer_stop(this);
+ }
+
+ private static void UvTimerCb(IntPtr handle)
+ {
+ var timer = FromIntPtr(handle);
+
+ try
+ {
+ timer._callback(timer);
+ }
+ catch (Exception ex)
+ {
+ timer._log.LogError(0, ex, nameof(UvTimerCb));
+ throw;
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Internal/UvWriteReq.cs b/Projects/Server/LibUv/Internal/UvWriteReq.cs
new file mode 100644
index 000000000..1b30c4ccb
--- /dev/null
+++ b/Projects/Server/LibUv/Internal/UvWriteReq.cs
@@ -0,0 +1,237 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv.Internal
+{
+ ///
+ /// Summary description for UvWriteRequest
+ ///
+ public class UvWriteReq : UvRequest
+ {
+ private static readonly LibuvFunctions.uv_write_cb _uv_write_cb = (ptr, status) => UvWriteCb(ptr, status);
+
+ private IntPtr _bufs;
+
+ private Action _callback;
+ private object _state;
+ private const int BUFFER_COUNT = 4;
+
+ private LibuvAwaitable _awaitable = new LibuvAwaitable();
+ private List _pins = new List(BUFFER_COUNT + 1);
+ private List _handles = new List(BUFFER_COUNT + 1);
+
+ public UvWriteReq(ILibuvTrace logger) : base(logger)
+ {
+ }
+
+ public override void Init(LibuvThread thread)
+ {
+ DangerousInit(thread.Loop);
+
+ base.Init(thread);
+ }
+
+ public void DangerousInit(UvLoopHandle loop)
+ {
+ var requestSize = loop.Libuv.req_size(LibuvFunctions.RequestType.WRITE);
+ var bufferSize = Marshal.SizeOf() * BUFFER_COUNT;
+ CreateMemory(
+ loop.Libuv,
+ loop.ThreadId,
+ requestSize + bufferSize);
+ _bufs = handle + requestSize;
+ }
+
+ public LibuvAwaitable WriteAsync(UvStreamHandle handle, in ReadOnlySequence buffer)
+ {
+ Write(handle, buffer, LibuvAwaitable.Callback, _awaitable);
+ return _awaitable;
+ }
+
+ public LibuvAwaitable WriteAsync(UvStreamHandle handle, ArraySegment> bufs)
+ {
+ Write(handle, bufs, LibuvAwaitable.Callback, _awaitable);
+ return _awaitable;
+ }
+
+ private unsafe void Write(
+ UvStreamHandle handle,
+ in ReadOnlySequence buffer,
+ Action callback,
+ object state)
+ {
+ try
+ {
+ var nBuffers = 0;
+ if (buffer.IsSingleSegment)
+ nBuffers = 1;
+ else
+ foreach (var _ in buffer)
+ nBuffers++;
+
+ var pBuffers = (LibuvFunctions.uv_buf_t*)_bufs;
+ if (nBuffers > BUFFER_COUNT)
+ {
+ // create and pin buffer array when it's larger than the pre-allocated one
+ var bufArray = new LibuvFunctions.uv_buf_t[nBuffers];
+ var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned);
+ _pins.Add(gcHandle);
+ pBuffers = (LibuvFunctions.uv_buf_t*)gcHandle.AddrOfPinnedObject();
+ }
+
+ if (nBuffers == 1)
+ {
+ var memory = buffer.First;
+ var memoryHandle = memory.Pin();
+ _handles.Add(memoryHandle);
+
+ // Fast path for single buffer
+ pBuffers[0] = Libuv.buf_init(
+ (IntPtr)memoryHandle.Pointer,
+ memory.Length);
+ }
+ else
+ {
+ var index = 0;
+ foreach (var memory in buffer)
+ {
+ // This won't actually pin the buffer since we're already using pinned memory
+ var memoryHandle = memory.Pin();
+ _handles.Add(memoryHandle);
+
+ // create and pin each segment being written
+ pBuffers[index] = Libuv.buf_init(
+ (IntPtr)memoryHandle.Pointer,
+ memory.Length);
+ index++;
+ }
+ }
+
+ _callback = callback;
+ _state = state;
+ _uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb);
+ }
+ catch
+ {
+ _callback = null;
+ _state = null;
+ UnpinGcHandles();
+ throw;
+ }
+ }
+
+ private void Write(
+ UvStreamHandle handle,
+ ArraySegment> bufs,
+ Action callback,
+ object state)
+ {
+ WriteArraySegmentInternal(handle, bufs, null, callback, state);
+ }
+
+ public void Write2(
+ UvStreamHandle handle,
+ ArraySegment> bufs,
+ UvStreamHandle sendHandle,
+ Action callback,
+ object state)
+ {
+ WriteArraySegmentInternal(handle, bufs, sendHandle, callback, state);
+ }
+
+ private unsafe void WriteArraySegmentInternal(
+ UvStreamHandle handle,
+ ArraySegment> bufs,
+ UvStreamHandle sendHandle,
+ Action callback,
+ object state)
+ {
+ try
+ {
+ var pBuffers = (LibuvFunctions.uv_buf_t*)_bufs;
+ var nBuffers = bufs.Count;
+ if (nBuffers > BUFFER_COUNT)
+ {
+ // create and pin buffer array when it's larger than the pre-allocated one
+ var bufArray = new LibuvFunctions.uv_buf_t[nBuffers];
+ var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned);
+ _pins.Add(gcHandle);
+ pBuffers = (LibuvFunctions.uv_buf_t*)gcHandle.AddrOfPinnedObject();
+ }
+
+ for (var index = 0; index < nBuffers; index++)
+ {
+ // create and pin each segment being written
+ var buf = bufs.Array[bufs.Offset + index];
+
+ var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned);
+ _pins.Add(gcHandle);
+ pBuffers[index] = Libuv.buf_init(
+ gcHandle.AddrOfPinnedObject() + buf.Offset,
+ buf.Count);
+ }
+
+ _callback = callback;
+ _state = state;
+
+ if (sendHandle == null)
+ _uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb);
+ else
+ _uv.write2(this, handle, pBuffers, nBuffers, sendHandle, _uv_write_cb);
+ }
+ catch
+ {
+ _callback = null;
+ _state = null;
+ UnpinGcHandles();
+ throw;
+ }
+ }
+
+ // Safe handle has instance method called Unpin
+ // so using UnpinGcHandles to avoid conflict
+ private void UnpinGcHandles()
+ {
+ var pinList = _pins;
+ var count = pinList.Count;
+ for (var i = 0; i < count; i++) pinList[i].Free();
+ pinList.Clear();
+
+ var handleList = _handles;
+ count = handleList.Count;
+ for (var i = 0; i < count; i++) handleList[i].Dispose();
+ handleList.Clear();
+ }
+
+ private static void UvWriteCb(IntPtr ptr, int status)
+ {
+ var req = FromIntPtr(ptr);
+ req.UnpinGcHandles();
+
+ var callback = req._callback;
+ req._callback = null;
+
+ var state = req._state;
+ req._state = null;
+
+ UvException error = null;
+ if (status < 0) req.Libuv.Check(status, out error);
+
+ try
+ {
+ callback(req, status, error, state);
+ }
+ catch (Exception ex)
+ {
+ req._log.LogError(0, ex, "UvWriteCb");
+ throw;
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvAwaitable.cs b/Projects/Server/LibUv/LibuvAwaitable.cs
new file mode 100644
index 000000000..efdaeae29
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvAwaitable.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using Libuv.Internal;
+
+namespace Libuv
+{
+ public class LibuvAwaitable : ICriticalNotifyCompletion where TRequest : UvRequest
+ {
+ private static readonly Action _callbackCompleted = () => { };
+
+ private Action _callback;
+
+ private UvException _exception;
+
+ private int _status;
+
+ public static readonly Action Callback = (req, status, error, state) =>
+ {
+ var awaitable = (LibuvAwaitable)state;
+
+ awaitable._exception = error;
+ awaitable._status = status;
+
+ var continuation = Interlocked.Exchange(ref awaitable._callback, _callbackCompleted);
+
+ continuation?.Invoke();
+ };
+
+ public LibuvAwaitable GetAwaiter() => this;
+ public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
+
+ public UvWriteResult GetResult()
+ {
+ Debug.Assert(_callback == _callbackCompleted);
+
+ var exception = _exception;
+ var status = _status;
+
+ // Reset the awaitable state
+ _exception = null;
+ _status = 0;
+ _callback = null;
+
+ return new UvWriteResult(status, exception);
+ }
+
+ public void OnCompleted(Action continuation)
+ {
+ // There should never be a race between IsCompleted and OnCompleted since both operations
+ // should always be on the libuv thread
+ if (ReferenceEquals(_callback, _callbackCompleted))
+ Debug.Fail($"{typeof(LibuvAwaitable)}.{nameof(OnCompleted)} raced with {nameof(IsCompleted)}, scheduling callback.");
+
+ _callback = continuation;
+ }
+
+ public void UnsafeOnCompleted(Action continuation)
+ {
+ OnCompleted(continuation);
+ }
+ }
+
+ public struct UvWriteResult
+ {
+ public int Status { get; }
+ public UvException Error { get; }
+
+ public UvWriteResult(int status, UvException error)
+ {
+ Status = status;
+ Error = error;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvConnection.cs b/Projects/Server/LibUv/LibuvConnection.cs
new file mode 100644
index 000000000..5839c4d6b
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvConnection.cs
@@ -0,0 +1,309 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.IO;
+using System.IO.Pipelines;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ public partial class LibuvConnection : TransportConnection
+ {
+ private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2;
+
+ private static readonly Action _readCallback = ReadCallback;
+
+ private static readonly Func _allocCallback = AllocCallback;
+
+ private readonly UvStreamHandle _socket;
+ private readonly CancellationTokenSource _connectionClosedTokenSource = new CancellationTokenSource();
+
+ private volatile ConnectionAbortedException _abortReason;
+
+ private MemoryHandle _bufferHandle;
+ private Task _processingTask;
+ private readonly TaskCompletionSource _waitForConnectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ private bool _connectionClosed;
+
+ public LibuvConnection(UvStreamHandle socket,
+ ILibuvTrace log,
+ LibuvThread thread,
+ IPEndPoint remoteEndPoint,
+ IPEndPoint localEndPoint,
+ PipeOptions inputOptions = null,
+ PipeOptions outputOptions = null,
+ long? maxReadBufferSize = null,
+ long? maxWriteBufferSize = null)
+ {
+ _socket = socket;
+
+ LocalEndPoint = localEndPoint;
+ RemoteEndPoint = remoteEndPoint;
+
+ ConnectionClosed = _connectionClosedTokenSource.Token;
+ Log = log;
+ Thread = thread;
+
+ maxReadBufferSize ??= 0;
+ maxWriteBufferSize ??= 0;
+
+ inputOptions ??= new PipeOptions(MemoryPool, PipeScheduler.ThreadPool, Thread, maxReadBufferSize.Value, maxReadBufferSize.Value / 2, useSynchronizationContext: false);
+ outputOptions ??= new PipeOptions(MemoryPool, Thread, PipeScheduler.ThreadPool, maxWriteBufferSize.Value, maxWriteBufferSize.Value / 2, useSynchronizationContext: false);
+
+ var pair = DuplexPipe.CreateConnectionPair(inputOptions, outputOptions);
+
+ // Set the transport and connection id
+ Transport = pair.Transport;
+ Application = pair.Application;
+ }
+
+ public PipeWriter Input => Application.Output;
+
+ public PipeReader Output => Application.Input;
+
+ public LibuvOutputConsumer OutputConsumer { get; set; }
+ private ILibuvTrace Log { get; }
+ private LibuvThread Thread { get; }
+ public override MemoryPool MemoryPool => Thread.MemoryPool;
+
+ public void Start()
+ {
+ _processingTask = StartCore();
+ }
+
+ private async Task StartCore()
+ {
+ try
+ {
+ OutputConsumer = new LibuvOutputConsumer(Output, Thread, _socket, ConnectionId, Log);
+
+ StartReading();
+
+ Exception inputError = null;
+ Exception outputError = null;
+
+ try
+ {
+ // This *must* happen after socket.ReadStart
+ // The socket output consumer is the only thing that can close the connection. If the
+ // output pipe is already closed by the time we start then it's fine since, it'll close gracefully afterwards.
+ await OutputConsumer.WriteOutputAsync();
+ }
+ catch (UvException ex)
+ {
+ // The connection reset/error has already been logged by LibuvOutputConsumer
+ if (ex.StatusCode == LibuvConstants.ECANCELED)
+ {
+ // Connection was aborted.
+ }
+ else if (LibuvConstants.IsConnectionReset(ex.StatusCode))
+ {
+ // Don't cause writes to throw for connection resets.
+ inputError = new ConnectionResetException(ex.Message, ex);
+ }
+ else
+ {
+ // This is unexpected.
+ Log.ConnectionError(ConnectionId, ex);
+
+ inputError = ex;
+ outputError = ex;
+ }
+ }
+ finally
+ {
+ inputError ??= _abortReason ?? new ConnectionAbortedException("The libuv transport's send loop completed gracefully.");
+
+ // Now, complete the input so that no more reads can happen
+ Input.Complete(inputError);
+ Output.Complete(outputError);
+
+ // Make sure it isn't possible for a paused read to resume reading after calling uv_close
+ // on the stream handle
+ Input.CancelPendingFlush();
+
+ // Send a FIN
+ Log.ConnectionWriteFin(ConnectionId, inputError.Message);
+
+ // We're done with the socket now
+ _socket.Dispose();
+
+ // Ensure this always fires
+ FireConnectionClosed();
+
+ await _waitForConnectionClosedTcs.Task;
+ }
+ }
+ catch (Exception e)
+ {
+ Log.LogCritical(0, e, $"{nameof(LibuvConnection)}.{nameof(Start)}() {ConnectionId}");
+ }
+ }
+
+ public override void Abort(ConnectionAbortedException abortReason)
+ {
+ _abortReason = abortReason;
+
+ // Cancel WriteOutputAsync loop after setting _abortReason.
+ Output.CancelPendingRead();
+
+ // This cancels any pending I/O.
+ Thread.Post(s => s.Dispose(), _socket);
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ Transport.Input.Complete();
+ Transport.Output.Complete();
+
+ if (_processingTask != null) await _processingTask;
+
+ _connectionClosedTokenSource.Dispose();
+ }
+
+ // Called on Libuv thread
+ private static LibuvFunctions.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state) => ((LibuvConnection)state).OnAlloc(handle, suggestedSize);
+
+ private unsafe LibuvFunctions.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize)
+ {
+ var currentWritableBuffer = Input.GetMemory(MinAllocBufferSize);
+ _bufferHandle = currentWritableBuffer.Pin();
+
+ return handle.Libuv.buf_init((IntPtr)_bufferHandle.Pointer, currentWritableBuffer.Length);
+ }
+
+ private static void ReadCallback(UvStreamHandle handle, int status, object state)
+ {
+ ((LibuvConnection)state).OnRead(handle, status);
+ }
+
+ private void OnRead(UvStreamHandle handle, int status)
+ {
+ // Cleanup state from last OnAlloc. This is safe even if OnAlloc wasn't called.
+ _bufferHandle.Dispose();
+ if (status == 0)
+ {
+ // EAGAIN/EWOULDBLOCK so just return the buffer.
+ // http://docs.libuv.org/en/v1.x/stream.html#c.uv_read_cb
+ }
+ else if (status > 0)
+ {
+ Log.ConnectionRead(ConnectionId, status);
+
+ Input.Advance(status);
+ var flushTask = Input.FlushAsync();
+
+ if (!flushTask.IsCompleted)
+ // We wrote too many bytes to the reader, so pause reading and resume when
+ // we hit the low water mark.
+ _ = ApplyBackpressureAsync(flushTask);
+ }
+ else
+ {
+ // Given a negative status, it's possible that OnAlloc wasn't called.
+ _socket.ReadStop();
+
+ Exception error = null;
+
+ if (status == LibuvConstants.EOF)
+ {
+ Log.ConnectionReadFin(ConnectionId);
+ }
+ else
+ {
+ handle.Libuv.Check(status, out var uvError);
+ error = LogAndWrapReadError(uvError);
+ }
+
+ FireConnectionClosed();
+
+ // Complete after aborting the connection
+ Input.Complete(error);
+ }
+ }
+
+ private void FireConnectionClosed()
+ {
+ // Guard against scheduling this multiple times
+ if (_connectionClosed) return;
+
+ _connectionClosed = true;
+
+ ThreadPool.UnsafeQueueUserWorkItem(state =>
+ {
+ state.CancelConnectionClosedToken();
+
+ state._waitForConnectionClosedTcs.TrySetResult(null);
+ },
+ this,
+ false);
+ }
+
+ private async Task ApplyBackpressureAsync(ValueTask flushTask)
+ {
+ Log.ConnectionPause(ConnectionId);
+ _socket.ReadStop();
+
+ var result = await flushTask;
+
+ // If the reader isn't complete or cancelled then resume reading
+ if (!result.IsCompleted && !result.IsCanceled)
+ {
+ Log.ConnectionResume(ConnectionId);
+ StartReading();
+ }
+ }
+
+ private void StartReading()
+ {
+ try
+ {
+ _socket.ReadStart(_allocCallback, _readCallback, this);
+ }
+ catch (UvException ex)
+ {
+ // ReadStart() can throw a UvException in some cases (e.g. socket is no longer connected).
+ // This should be treated the same as OnRead() seeing a negative status.
+ Input.Complete(LogAndWrapReadError(ex));
+ }
+ }
+
+ private Exception LogAndWrapReadError(UvException uvError)
+ {
+ if (uvError.StatusCode == LibuvConstants.ECANCELED)
+ {
+ // The operation was canceled by the server not the client. No need for additional logs.
+ return new ConnectionAbortedException(uvError.Message, uvError);
+ }
+
+ if (LibuvConstants.IsConnectionReset(uvError.StatusCode))
+ {
+ // Log connection resets at a lower (Debug) level.
+ Log.ConnectionReset(ConnectionId);
+ return new ConnectionResetException(uvError.Message, uvError);
+ }
+ // This is unexpected.
+ Log.ConnectionError(ConnectionId, uvError);
+ return new IOException(uvError.Message, uvError);
+ }
+
+ private void CancelConnectionClosedToken()
+ {
+ try
+ {
+ _connectionClosedTokenSource.Cancel();
+ }
+ catch (Exception ex)
+ {
+ Log.LogError(0, ex, $"Unexpected exception in {nameof(LibuvConnection)}.{nameof(CancelConnectionClosedToken)}.");
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvConnectionListener.cs b/Projects/Server/LibUv/LibuvConnectionListener.cs
new file mode 100644
index 000000000..d04da54c6
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvConnectionListener.cs
@@ -0,0 +1,196 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Server;
+
+namespace Libuv
+{
+ public class LibuvConnectionListener : IConnectionListener
+ {
+ private readonly List _listeners = new List();
+ private IAsyncEnumerator _acceptEnumerator;
+ private bool _stopped;
+ private bool _disposed;
+
+ public LibuvConnectionListener(LibuvTransportContext context, EndPoint endPoint)
+ : this(new LibuvFunctions(), context, endPoint)
+ { }
+
+ // For testing
+ public LibuvConnectionListener(LibuvFunctions uv, LibuvTransportContext context, EndPoint endPoint)
+ {
+ Libuv = uv;
+ TransportContext = context;
+
+ EndPoint = endPoint;
+ }
+
+ public LibuvFunctions Libuv { get; }
+ public LibuvTransportContext TransportContext { get; }
+ public List Threads { get; } = new List();
+
+ public IHostApplicationLifetime AppLifetime => TransportContext.AppLifetime;
+ public ILibuvTrace Log => TransportContext.Log;
+ public LibuvTransportOptions TransportOptions => TransportContext.Options;
+
+ public EndPoint EndPoint { get; set; }
+
+ public async ValueTask AcceptAsync(CancellationToken cancellationToken = default)
+ {
+ if (_disposed) throw new ObjectDisposedException(GetType().FullName);
+
+ if (await _acceptEnumerator.MoveNextAsync()) return _acceptEnumerator.Current;
+
+ // null means we're done...
+ return null;
+ }
+
+ public async ValueTask UnbindAsync(CancellationToken cancellationToken = default)
+ {
+ if (_stopped) return;
+
+ _stopped = true;
+
+ var disposeTasks = _listeners.Select(listener => ((IAsyncDisposable)listener).DisposeAsync()).ToArray();
+
+ if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false))
+ Log.LogError(0, null, "Disposing listeners failed");
+ }
+
+
+ public async ValueTask DisposeAsync()
+ {
+ if (_disposed) return;
+
+ _disposed = true;
+
+ await UnbindAsync().ConfigureAwait(false);
+
+ foreach (var listener in _listeners) await listener.AbortQueuedConnectionAsync().ConfigureAwait(false);
+
+ _listeners.Clear();
+
+ await StopThreadsAsync().ConfigureAwait(false);
+ }
+
+ internal async Task StopThreadsAsync()
+ {
+ try
+ {
+ await Task.WhenAll(Threads.Select(thread => thread.StopAsync(TimeSpan.FromSeconds(5))).ToArray())
+ .ConfigureAwait(false);
+ }
+ catch (AggregateException aggEx)
+ {
+ // An uncaught exception was likely thrown from the libuv event loop.
+ // The original error that crashed one loop may have caused secondary errors in others.
+ // Make sure that the stack trace of the original error is logged.
+ foreach (var ex in aggEx.InnerExceptions) Log.LogCritical("Failed to gracefully close Kestrel.", ex);
+
+ throw;
+ }
+
+ Threads.Clear();
+#if DEBUG && !INNER_LOOP
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+#endif
+ }
+
+ public async Task BindAsync()
+ {
+ // TODO: Move thread management to LibuvTransportFactory
+ // TODO: Split endpoint management from thread management
+ for (var index = 0; index < TransportOptions.ThreadCount; index++) Threads.Add(new LibuvThread(Libuv, TransportContext));
+
+ foreach (var thread in Threads) await thread.StartAsync().ConfigureAwait(false);
+
+ try
+ {
+ if (TransportOptions.ThreadCount == 1)
+ {
+ var listener = new Listener(TransportContext);
+ _listeners.Add(listener);
+ await listener.StartAsync(EndPoint, Threads[0]).ConfigureAwait(false);
+ EndPoint = listener.EndPoint;
+ }
+ else
+ {
+ var pipeName = (Core.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
+ var pipeMessage = Guid.NewGuid().ToByteArray();
+
+ var listenerPrimary = new ListenerPrimary(TransportContext);
+ _listeners.Add(listenerPrimary);
+ await listenerPrimary.StartAsync(pipeName, pipeMessage, EndPoint, Threads[0]).ConfigureAwait(false);
+ EndPoint = listenerPrimary.EndPoint;
+
+ foreach (var thread in Threads.Skip(1))
+ {
+ var listenerSecondary = new ListenerSecondary(TransportContext);
+ _listeners.Add(listenerSecondary);
+ await listenerSecondary.StartAsync(pipeName, pipeMessage, EndPoint, thread).ConfigureAwait(false);
+ }
+ }
+ _acceptEnumerator = AcceptConnections();
+ }
+ catch (UvException ex) when (ex.StatusCode == LibuvConstants.EADDRINUSE)
+ {
+ await UnbindAsync().ConfigureAwait(false);
+ throw new AddressInUseException(ex.Message, ex);
+ }
+ catch
+ {
+ await UnbindAsync().ConfigureAwait(false);
+ throw;
+ }
+ }
+
+ private async IAsyncEnumerator AcceptConnections()
+ {
+ var slots = new Task<(LibuvConnection, int)>[_listeners.Count];
+ // This is the task we'll put in the slot when each listening completes. It'll prevent
+ // us from having to shrink the array. We'll just loop while there are active slots.
+ var incompleteTask = new TaskCompletionSource<(LibuvConnection, int)>().Task;
+
+ var remainingSlots = slots.Length;
+
+ // Issue parallel accepts on all listeners
+ for (int i = 0; i < remainingSlots; i++) slots[i] = AcceptAsync(_listeners[i], i);
+
+ while (remainingSlots > 0)
+ {
+ // Calling GetAwaiter().GetResult() is safe because we know the task is completed
+ var (connection, slot) = (await Task.WhenAny(slots)).GetAwaiter().GetResult();
+
+ // If the connection is null then the listener was closed
+ if (connection == null)
+ {
+ remainingSlots--;
+ slots[slot] = incompleteTask;
+ }
+ else
+ {
+ // Fill that slot with another accept and yield the connection
+ slots[slot] = AcceptAsync(_listeners[slot], slot);
+
+ yield return connection;
+ }
+ }
+
+ static async Task<(LibuvConnection, int)> AcceptAsync(ListenerContext listener, int slot) => (await listener.AcceptAsync(), slot);
+ }
+
+ private static async Task WaitAsync(Task task, TimeSpan timeout) => await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvConstants.cs b/Projects/Server/LibUv/LibuvConstants.cs
new file mode 100644
index 000000000..bce142a81
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvConstants.cs
@@ -0,0 +1,98 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+using Server;
+
+namespace Libuv
+{
+ public static class LibuvConstants
+ {
+ public const int EOF = -4095;
+ public static readonly int? ECONNRESET = GetECONNRESET();
+ public static readonly int? EADDRINUSE = GetEADDRINUSE();
+ public static readonly int? ENOTSUP = GetENOTSUP();
+ public static readonly int? EPIPE = GetEPIPE();
+ public static readonly int? ECANCELED = GetECANCELED();
+ public static readonly int? ENOTCONN = GetENOTCONN();
+ public static readonly int? EINVAL = GetEINVAL();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsConnectionReset(int errno) => errno == ECONNRESET || errno == EPIPE || errno == ENOTCONN || errno == EINVAL;
+
+ private static int? GetECONNRESET()
+ {
+ if (Core.IsWindows)
+ return -4077;
+ if (Core.IsLinux)
+ return -104;
+ if (Core.IsDarwin)
+ return -5;
+ return null;
+ }
+
+ private static int? GetEPIPE()
+ {
+ if (Core.IsWindows)
+ return -4047;
+ if (Core.IsLinux)
+ return -32;
+ if (Core.IsDarwin)
+ return -32;
+ return null;
+ }
+
+ private static int? GetENOTCONN()
+ {
+ if (Core.IsWindows)
+ return -4053;
+ if (Core.IsLinux)
+ return -107;
+ if (Core.IsDarwin)
+ return -57;
+ return null;
+ }
+
+ private static int? GetEINVAL()
+ {
+ if (Core.IsWindows)
+ return -4071;
+ if (Core.IsLinux)
+ return -22;
+ if (Core.IsDarwin)
+ return -22;
+ return null;
+ }
+
+ private static int? GetEADDRINUSE()
+ {
+ if (Core.IsWindows)
+ return -4091;
+ if (Core.IsLinux)
+ return -98;
+ if (Core.IsDarwin)
+ return -48;
+ return null;
+ }
+
+ private static int? GetENOTSUP()
+ {
+ if (Core.IsLinux)
+ return -95;
+ if (Core.IsDarwin)
+ return -45;
+ return null;
+ }
+
+ private static int? GetECANCELED()
+ {
+ if (Core.IsWindows)
+ return -4081;
+ if (Core.IsLinux)
+ return -125;
+ if (Core.IsDarwin)
+ return -89;
+ return null;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvOutputConsumer.cs b/Projects/Server/LibUv/LibuvOutputConsumer.cs
new file mode 100644
index 000000000..e9d42ca52
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvOutputConsumer.cs
@@ -0,0 +1,111 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO.Pipelines;
+using System.Threading.Tasks;
+using Libuv.Internal;
+
+namespace Libuv
+{
+ public class LibuvOutputConsumer
+ {
+ private readonly LibuvThread _thread;
+ private readonly UvStreamHandle _socket;
+ private readonly string _connectionId;
+ private readonly ILibuvTrace _log;
+ private readonly PipeReader _pipe;
+
+ public LibuvOutputConsumer(
+ PipeReader pipe,
+ LibuvThread thread,
+ UvStreamHandle socket,
+ string connectionId,
+ ILibuvTrace log)
+ {
+ _pipe = pipe;
+ _thread = thread;
+ _socket = socket;
+ _connectionId = connectionId;
+ _log = log;
+ }
+
+ public async Task WriteOutputAsync()
+ {
+ var pool = _thread.WriteReqPool;
+
+ while (true)
+ {
+ var result = await _pipe.ReadAsync();
+
+ var buffer = result.Buffer;
+ var consumed = buffer.End;
+
+ try
+ {
+ if (result.IsCanceled) break;
+
+ if (!buffer.IsEmpty)
+ {
+ var writeReq = pool.Allocate();
+
+ try
+ {
+ if (_socket.IsClosed) break;
+
+ var writeResult = await writeReq.WriteAsync(_socket, buffer);
+
+ LogWriteInfo(writeResult.Status, writeResult.Error);
+
+ if (writeResult.Error != null)
+ {
+ consumed = buffer.Start;
+ throw writeResult.Error;
+ }
+ }
+ finally
+ {
+ // Make sure we return the writeReq to the pool
+ pool.Return(writeReq);
+
+ // Null out writeReq so it doesn't get caught by CheckUvReqLeaks.
+ // It is rooted by a TestSink scope through Pipe continuations in
+ // ResponseTests.HttpsConnectionClosedWhenResponseDoesNotSatisfyMinimumDataRate
+ writeReq = null;
+ }
+ }
+
+ if (result.IsCompleted) break;
+ }
+ finally
+ {
+ _pipe.AdvanceTo(consumed);
+ }
+ }
+ }
+
+ private void LogWriteInfo(int status, Exception error)
+ {
+ if (error == null)
+ {
+ _log.ConnectionWriteCallback(_connectionId, status);
+ }
+ else
+ {
+ // Log connection resets at a lower (Debug) level.
+ if (status == LibuvConstants.ECANCELED)
+ {
+ // Connection was aborted.
+ }
+ else if (LibuvConstants.IsConnectionReset(status))
+ {
+ _log.ConnectionReset(_connectionId);
+ }
+ else
+ {
+ _log.ConnectionError(_connectionId, error);
+ }
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvThread.cs b/Projects/Server/LibUv/LibuvThread.cs
new file mode 100644
index 000000000..d57d7f2dc
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvThread.cs
@@ -0,0 +1,437 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO.Pipelines;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ public class LibuvThread : PipeScheduler
+ {
+ // maximum times the work queues swapped and are processed in a single pass
+ // as completing a task may immediately have write data to put on the network
+ // otherwise it needs to wait till the next pass of the libuv loop
+ private readonly int _maxLoops;
+
+ private readonly LibuvFunctions _libuv;
+ private readonly IHostApplicationLifetime _appLifetime;
+ private readonly Thread _thread;
+ private readonly TaskCompletionSource _threadTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly UvLoopHandle _loop;
+ private readonly UvAsyncHandle _post;
+ private Queue _workAdding = new Queue(1024);
+ private Queue _workRunning = new Queue(1024);
+ private Queue _closeHandleAdding = new Queue(256);
+ private Queue _closeHandleRunning = new Queue(256);
+ private readonly object _workSync = new object();
+ private readonly object _closeHandleSync = new object();
+ private readonly object _startSync = new object();
+ private bool _stopImmediate;
+ private bool _initCompleted;
+ private Exception _closeError;
+ private readonly ILibuvTrace _log;
+
+ public LibuvThread(LibuvFunctions libuv, LibuvTransportContext libuvTransportContext, int maxLoops = 8)
+ : this(libuv, libuvTransportContext.AppLifetime, libuvTransportContext.Options.MemoryPoolFactory(), libuvTransportContext.Log, maxLoops)
+ {
+ }
+
+ public LibuvThread(LibuvFunctions libuv, IHostApplicationLifetime appLifetime, MemoryPool pool, ILibuvTrace log, int maxLoops = 8)
+ {
+ _libuv = libuv;
+ _appLifetime = appLifetime;
+ _log = log;
+ _loop = new UvLoopHandle(_log);
+ _post = new UvAsyncHandle(_log);
+ _maxLoops = maxLoops;
+
+ _thread = new Thread(ThreadStart);
+#if !INNER_LOOP
+ _thread.Name = nameof(LibuvThread);
+#endif
+
+#if !DEBUG
+ // Mark the thread as being as unimportant to keeping the process alive.
+ // Don't do this for debug builds, so we know if the thread isn't terminating.
+ _thread.IsBackground = true;
+#endif
+ QueueCloseHandle = PostCloseHandle;
+ QueueCloseAsyncHandle = EnqueueCloseHandle;
+ MemoryPool = pool;
+ WriteReqPool = new WriteReqPool(this, _log);
+ }
+
+ public UvLoopHandle Loop => _loop;
+
+ public MemoryPool MemoryPool { get; }
+
+ public WriteReqPool WriteReqPool { get; }
+
+#if DEBUG
+ public List Requests { get; } = new List();
+#endif
+
+ public Exception FatalError => _closeError;
+
+ public Action, IntPtr> QueueCloseHandle { get; }
+
+ private Action, IntPtr> QueueCloseAsyncHandle { get; }
+
+ public Task StartAsync()
+ {
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ _thread.Start(tcs);
+ return tcs.Task;
+ }
+
+ public async Task StopAsync(TimeSpan timeout)
+ {
+ lock (_startSync)
+ {
+ if (!_initCompleted) return;
+ }
+
+ Debug.Assert(!_threadTcs.Task.IsCompleted, "The loop thread was completed before calling uv_unref on the post handle.");
+
+ var stepTimeout = TimeSpan.FromTicks(timeout.Ticks / 3);
+
+ try
+ {
+ Post(t => t.AllowStop());
+ if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false))
+ {
+ _log.LogWarning($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(AllowStop)}");
+
+ Post(t => t.OnStopRude());
+ if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false))
+ {
+ _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(OnStopRude)}.");
+
+ Post(t => t.OnStopImmediate());
+ if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread, {nameof(OnStopImmediate)}.");
+ }
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread.");
+ }
+
+ if (_closeError != null) ExceptionDispatchInfo.Capture(_closeError).Throw();
+ }
+
+#if DEBUG && !INNER_LOOP
+ private void CheckUvReqLeaks()
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ // Detect leaks in UvRequest objects
+ foreach (var request in Requests) Debug.Assert(request.Target == null, $"{request.Target?.GetType()} object is still alive.");
+ }
+#endif
+
+ private void AllowStop()
+ {
+ _post.Unreference();
+ }
+
+ private void OnStopRude()
+ {
+ Walk(ptr =>
+ {
+ var handle = UvMemory.FromIntPtr(ptr);
+ if (handle != _post)
+ // handle can be null because UvMemory.FromIntPtr looks up a weak reference
+ handle?.Dispose();
+ });
+ }
+
+ private void OnStopImmediate()
+ {
+ _stopImmediate = true;
+ _loop.Stop();
+ }
+
+ public void Post(Action callback, T state)
+ {
+ // Handle is closed to don't bother scheduling anything
+ if (_post.IsClosed) return;
+
+ var work = new Work
+ {
+ CallbackAdapter = CallbackAdapter.PostCallbackAdapter,
+ Callback = callback,
+ // TODO: This boxes
+ State = state
+ };
+
+ lock (_workSync)
+ {
+ _workAdding.Enqueue(work);
+ }
+
+ try
+ {
+ _post.Send();
+ }
+ catch (ObjectDisposedException)
+ {
+ // There's an inherent race here where we're in the middle of shutdown
+ }
+ }
+
+ private void Post(Action callback)
+ {
+ Post(callback, this);
+ }
+
+ public Task PostAsync(Action callback, T state)
+ {
+ // Handle is closed to don't bother scheduling anything
+ if (_post.IsClosed) return Task.CompletedTask;
+
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var work = new Work
+ {
+ CallbackAdapter = CallbackAdapter.PostAsyncCallbackAdapter,
+ Callback = callback,
+ State = state,
+ Completion = tcs
+ };
+
+ lock (_workSync)
+ {
+ _workAdding.Enqueue(work);
+ }
+
+ try
+ {
+ _post.Send();
+ }
+ catch (ObjectDisposedException)
+ {
+ // There's an inherent race here where we're in the middle of shutdown
+ }
+ return tcs.Task;
+ }
+
+ public void Walk(Action callback)
+ {
+ Walk((ptr, arg) => callback(ptr), IntPtr.Zero);
+ }
+
+ private void Walk(LibuvFunctions.uv_walk_cb callback, IntPtr arg)
+ {
+ _libuv.walk(
+ _loop,
+ callback,
+ arg
+ );
+ }
+
+ private void PostCloseHandle(Action callback, IntPtr handle)
+ {
+ EnqueueCloseHandle(callback, handle);
+ _post.Send();
+ }
+
+ private void EnqueueCloseHandle(Action callback, IntPtr handle)
+ {
+ var closeHandle = new CloseHandle { Callback = callback, Handle = handle };
+ lock (_closeHandleSync)
+ {
+ _closeHandleAdding.Enqueue(closeHandle);
+ }
+ }
+
+ private void ThreadStart(object parameter)
+ {
+ lock (_startSync)
+ {
+ var tcs = (TaskCompletionSource)parameter;
+ try
+ {
+ _loop.Init(_libuv);
+ _post.Init(_loop, OnPost, EnqueueCloseHandle);
+ _initCompleted = true;
+ tcs.SetResult(0);
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ return;
+ }
+ }
+
+ try
+ {
+ _loop.Run();
+ if (_stopImmediate)
+ // thread-abort form of exit, resources will be leaked
+ return;
+
+ // run the loop one more time to delete the open handles
+ _post.Reference();
+ _post.Dispose();
+
+ // We need this walk because we call ReadStop on accepted connections when there's back pressure
+ // Calling ReadStop makes the handle as in-active which means the loop can
+ // end while there's still valid handles around. This makes loop.Dispose throw
+ // with an EBUSY. To avoid that, we walk all of the handles and dispose them.
+ Walk(ptr =>
+ {
+ var handle = UvMemory.FromIntPtr(ptr);
+ // handle can be null because UvMemory.FromIntPtr looks up a weak reference
+ handle?.Dispose();
+ });
+
+ // Ensure the Dispose operations complete in the event loop.
+ _loop.Run();
+
+ _loop.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _closeError = ex;
+ // Request shutdown so we can rethrow this exception
+ // in Stop which should be observable.
+ _appLifetime.StopApplication();
+ }
+ finally
+ {
+ try
+ {
+ MemoryPool.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _closeError = _closeError == null ? ex : new AggregateException(_closeError, ex);
+ }
+ WriteReqPool.Dispose();
+ _threadTcs.SetResult(null);
+
+#if DEBUG && !INNER_LOOP
+ // Check for handle leaks after disposing everything
+ CheckUvReqLeaks();
+#endif
+ }
+ }
+
+ private void OnPost()
+ {
+ var loopsRemaining = _maxLoops;
+ bool wasWork;
+ do
+ {
+ wasWork = DoPostWork();
+ wasWork = DoPostCloseHandle() || wasWork;
+ loopsRemaining--;
+ } while (wasWork && loopsRemaining > 0);
+ }
+
+ private bool DoPostWork()
+ {
+ Queue queue;
+ lock (_workSync)
+ {
+ queue = _workAdding;
+ _workAdding = _workRunning;
+ _workRunning = queue;
+ }
+
+ bool wasWork = queue.Count > 0;
+
+ while (queue.Count != 0)
+ {
+ var work = queue.Dequeue();
+ try
+ {
+ work.CallbackAdapter(work.Callback, work.State);
+ work.Completion?.TrySetResult(null);
+ }
+ catch (Exception ex)
+ {
+ if (work.Completion != null)
+ {
+ work.Completion.TrySetException(ex);
+ }
+ else
+ {
+ _log.LogError(0, ex, $"{nameof(LibuvThread)}.{nameof(DoPostWork)}");
+ throw;
+ }
+ }
+ }
+
+ return wasWork;
+ }
+
+ private bool DoPostCloseHandle()
+ {
+ Queue queue;
+ lock (_closeHandleSync)
+ {
+ queue = _closeHandleAdding;
+ _closeHandleAdding = _closeHandleRunning;
+ _closeHandleRunning = queue;
+ }
+
+ bool wasWork = queue.Count > 0;
+
+ while (queue.Count != 0)
+ {
+ var closeHandle = queue.Dequeue();
+ try
+ {
+ closeHandle.Callback(closeHandle.Handle);
+ }
+ catch (Exception ex)
+ {
+ _log.LogError(0, ex, $"{nameof(LibuvThread)}.{nameof(DoPostCloseHandle)}");
+ throw;
+ }
+ }
+
+ return wasWork;
+ }
+
+ private static async Task WaitAsync(Task task, TimeSpan timeout) =>
+ await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
+
+ public override void Schedule(Action action, object state)
+ {
+ Post(action, state);
+ }
+
+ private struct Work
+ {
+ public Action CallbackAdapter;
+ public object Callback;
+ public object State;
+ public TaskCompletionSource Completion;
+ }
+
+ private struct CloseHandle
+ {
+ public Action Callback;
+ public IntPtr Handle;
+ }
+
+ private class CallbackAdapter
+ {
+ public static readonly Action PostCallbackAdapter = (callback, state) => ((Action)callback).Invoke((T)state);
+ public static readonly Action PostAsyncCallbackAdapter = (callback, state) => ((Action)callback).Invoke((T)state);
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvTrace.cs b/Projects/Server/LibUv/LibuvTrace.cs
new file mode 100644
index 000000000..ebc090316
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvTrace.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ public class LibuvTrace : ILibuvTrace
+ {
+ // ConnectionRead: Reserved: 3
+
+ private static readonly Action _connectionPause =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(4, nameof(ConnectionPause)), @"Connection id ""{ConnectionId}"" paused.");
+
+ private static readonly Action _connectionResume =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(5, nameof(ConnectionResume)), @"Connection id ""{ConnectionId}"" resumed.");
+
+ private static readonly Action _connectionReadFin =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
+
+ private static readonly Action _connectionWriteFin =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""");
+
+ // ConnectionWrite: Reserved: 11
+
+ // ConnectionWriteCallback: Reserved: 12
+
+ private static readonly Action _connectionError =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(14, nameof(ConnectionError)), @"Connection id ""{ConnectionId}"" communication error.");
+
+ private static readonly Action _connectionReset =
+ LoggerMessage.Define(LogLevel.Debug, new EventId(19, nameof(ConnectionReset)), @"Connection id ""{ConnectionId}"" reset.");
+
+ private readonly ILogger _logger;
+
+ public LibuvTrace(ILogger logger) => _logger = logger;
+
+ public void ConnectionRead(string connectionId, int count)
+ {
+ // Don't log for now since this could be *too* verbose.
+ // Reserved: Event ID 3
+ }
+
+ public void ConnectionReadFin(string connectionId)
+ {
+ _connectionReadFin(_logger, connectionId, null);
+ }
+
+ public void ConnectionWriteFin(string connectionId, string reason)
+ {
+ _connectionWriteFin(_logger, connectionId, reason, null);
+ }
+
+ public void ConnectionWrite(string connectionId, int count)
+ {
+ // Don't log for now since this could be *too* verbose.
+ // Reserved: Event ID 11
+ }
+
+ public void ConnectionWriteCallback(string connectionId, int status)
+ {
+ // Don't log for now since this could be *too* verbose.
+ // Reserved: Event ID 12
+ }
+
+ public void ConnectionError(string connectionId, Exception ex)
+ {
+ _connectionError(_logger, connectionId, ex);
+ }
+
+ public void ConnectionReset(string connectionId)
+ {
+ _connectionReset(_logger, connectionId, null);
+ }
+
+ public void ConnectionPause(string connectionId)
+ {
+ _connectionPause(_logger, connectionId, null);
+ }
+
+ public void ConnectionResume(string connectionId)
+ {
+ _connectionResume(_logger, connectionId, null);
+ }
+
+ public IDisposable BeginScope(TState state) => _logger.BeginScope(state);
+
+ public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ => _logger.Log(logLevel, eventId, state, exception, formatter);
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvTransportContext.cs b/Projects/Server/LibUv/LibuvTransportContext.cs
new file mode 100644
index 000000000..730f31eb3
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvTransportContext.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Hosting;
+
+namespace Libuv
+{
+ public class LibuvTransportContext
+ {
+ public LibuvTransportOptions Options { get; set; }
+
+ public IHostApplicationLifetime AppLifetime { get; set; }
+
+ public ILibuvTrace Log { get; set; }
+ }
+}
diff --git a/Projects/Server/LibUv/LibuvTransportOptions.cs b/Projects/Server/LibUv/LibuvTransportOptions.cs
new file mode 100644
index 000000000..9de07cc83
--- /dev/null
+++ b/Projects/Server/LibUv/LibuvTransportOptions.cs
@@ -0,0 +1,51 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+
+namespace Libuv
+{
+ ///
+ /// Provides programmatic configuration of Libuv transport features.
+ ///
+ public class LibuvTransportOptions
+ {
+ ///
+ /// The number of libuv I/O threads used to process requests.
+ ///
+ ///
+ /// Defaults to half of rounded down and clamped between 1 and 16.
+ ///
+ public int ThreadCount { get; set; } = ProcessorThreadCount;
+
+ ///
+ /// The maximum length of the pending connection queue.
+ ///
+ ///
+ /// Defaults to 128.
+ ///
+ public int Backlog { get; set; } = 128;
+
+ public long? MaxReadBufferSize { get; set; } = 1024 * 1024;
+
+ public long? MaxWriteBufferSize { get; set; } = 64 * 1024;
+
+ internal Func> MemoryPoolFactory { get; set; } = SlabMemoryPoolFactory.Create;
+
+ private static int ProcessorThreadCount
+ {
+ get
+ {
+ // Actual core count would be a better number
+ // rather than logical cores which includes hyper-threaded cores.
+ // Divide by 2 for hyper-threading, and good defaults (still need threads for the game thread).
+ var threadCount = Environment.ProcessorCount >> 1;
+
+ // Receive Side Scaling RSS Processor count currently maxes out at 16
+ // would be better to check the NIC's current hardware queues; but xplat...
+ return threadCount < 1 ? 1 : threadCount > 16 ? 16 : threadCount;
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/Listener.cs b/Projects/Server/LibUv/Listener.cs
new file mode 100644
index 000000000..4ed799b78
--- /dev/null
+++ b/Projects/Server/LibUv/Listener.cs
@@ -0,0 +1,205 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ ///
+ /// Base class for listeners in Kestrel. Listens for incoming connections
+ ///
+ public class Listener : ListenerContext, IAsyncDisposable
+ {
+ // REVIEW: This needs to be bounded and we need a strategy for what to do when the queue is full
+ private bool _closed;
+
+ public Listener(LibuvTransportContext transportContext) : base(transportContext)
+ {
+ }
+
+ protected UvStreamHandle ListenSocket { get; private set; }
+
+ public ILibuvTrace Log => TransportContext.Log;
+
+ public Task StartAsync(
+ EndPoint endPoint,
+ LibuvThread thread)
+ {
+ EndPoint = endPoint;
+ Thread = thread;
+
+ return Thread.PostAsync(listener =>
+ {
+ listener.ListenSocket = listener.CreateListenSocket();
+ listener.ListenSocket.Listen(TransportContext.Options.Backlog, ConnectionCallback, listener);
+ }, this);
+ }
+
+ ///
+ /// Creates the socket used to listen for incoming connections
+ ///
+ private UvStreamHandle CreateListenSocket()
+ {
+ return EndPoint switch
+ {
+ IPEndPoint _ => ListenTcp(false),
+ UnixDomainSocketEndPoint _ => ListenPipe(false),
+ FileHandleEndPoint _ => ListenHandle(),
+ _ => throw new NotSupportedException()
+ };
+ }
+
+ private UvTcpHandle ListenTcp(bool useFileHandle)
+ {
+ var socket = new UvTcpHandle(Log);
+
+ try
+ {
+ socket.Init(Thread.Loop, Thread.QueueCloseHandle);
+ socket.NoDelay(true);
+
+ if (!useFileHandle)
+ {
+ socket.Bind((IPEndPoint)EndPoint);
+
+ // If requested port was "0", replace with assigned dynamic port.
+ EndPoint = socket.GetSockIPEndPoint();
+ }
+ else
+ {
+ socket.Open((IntPtr)((FileHandleEndPoint)EndPoint).FileHandle);
+ }
+ }
+ catch
+ {
+ socket.Dispose();
+ throw;
+ }
+
+ return socket;
+ }
+
+ private UvPipeHandle ListenPipe(bool useFileHandle)
+ {
+ var pipe = new UvPipeHandle(Log);
+
+ try
+ {
+ pipe.Init(Thread.Loop, Thread.QueueCloseHandle, false);
+
+ if (!useFileHandle)
+ // UnixDomainSocketEndPoint.ToString() returns the path
+ pipe.Bind(EndPoint.ToString());
+ else
+ pipe.Open((IntPtr)((FileHandleEndPoint)EndPoint).FileHandle);
+ }
+ catch
+ {
+ pipe.Dispose();
+ throw;
+ }
+
+ return pipe;
+ }
+
+ private UvStreamHandle ListenHandle()
+ {
+ var handleEndPoint = (FileHandleEndPoint)EndPoint;
+
+ switch (handleEndPoint.FileHandleType)
+ {
+ case FileHandleType.Auto:
+ break;
+ case FileHandleType.Tcp:
+ return ListenTcp(true);
+ case FileHandleType.Pipe:
+ return ListenPipe(true);
+ default:
+ throw new NotSupportedException();
+ }
+
+ UvStreamHandle handle;
+ try
+ {
+ handle = ListenTcp(true);
+ EndPoint = new FileHandleEndPoint(handleEndPoint.FileHandle, FileHandleType.Tcp);
+ return handle;
+ }
+ catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP)
+ {
+ Log.LogDebug(0, exception, "Listener.ListenHandle");
+ }
+
+ handle = ListenPipe(true);
+ EndPoint = new FileHandleEndPoint(handleEndPoint.FileHandle, FileHandleType.Pipe);
+ return handle;
+ }
+
+ private static void ConnectionCallback(UvStreamHandle stream, int status, UvException error, object state)
+ {
+ var listener = (Listener)state;
+
+ if (error != null)
+ listener.Log.LogError(0, error, "Listener.ConnectionCallback");
+ else if (!listener._closed) listener.OnConnection(stream, status);
+ }
+
+ ///
+ /// Handles an incoming connection
+ ///
+ /// Socket being used to listen on
+ /// Connection status
+ private void OnConnection(UvStreamHandle listenSocket, int status)
+ {
+ UvStreamHandle acceptSocket = null;
+
+ try
+ {
+ acceptSocket = CreateAcceptSocket();
+ listenSocket.Accept(acceptSocket);
+ DispatchConnection(acceptSocket);
+ }
+ catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode))
+ {
+ Log.ConnectionReset("(null)");
+ acceptSocket?.Dispose();
+ }
+ catch (UvException ex)
+ {
+ Log.LogError(0, ex, "Listener.OnConnection");
+ acceptSocket?.Dispose();
+ }
+ }
+
+ protected virtual void DispatchConnection(UvStreamHandle socket)
+ {
+ HandleConnection(socket);
+ }
+
+ public virtual async Task DisposeAsync()
+ {
+ // Ensure the event loop is still running.
+ // If the event loop isn't running and we try to wait on this Post
+ // to complete, then LibuvTransport will never be disposed and
+ // the exception that stopped the event loop will never be surfaced.
+ if (Thread.FatalError == null && ListenSocket != null)
+ await Thread.PostAsync(listener =>
+ {
+ listener.ListenSocket.Dispose();
+
+ listener._closed = true;
+
+ listener.StopAcceptingConnections();
+
+ }, this).ConfigureAwait(false);
+
+ ListenSocket = null;
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/ListenerContext.cs b/Projects/Server/LibUv/ListenerContext.cs
new file mode 100644
index 000000000..52b5b9149
--- /dev/null
+++ b/Projects/Server/LibUv/ListenerContext.cs
@@ -0,0 +1,168 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.IO.Pipelines;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ public class ListenerContext
+ {
+ // Single reader, single writer queue since all writes happen from the uv thread and reads happen sequentially
+ private readonly Channel _acceptQueue = Channel.CreateUnbounded(new UnboundedChannelOptions
+ {
+ SingleReader = true,
+ SingleWriter = true
+ });
+
+ public ListenerContext(LibuvTransportContext transportContext) => TransportContext = transportContext;
+
+ public LibuvTransportContext TransportContext { get; set; }
+
+ public EndPoint EndPoint { get; set; }
+
+ public LibuvThread Thread { get; set; }
+
+ public PipeOptions InputOptions { get; set; }
+
+ public PipeOptions OutputOptions { get; set; }
+
+ public async ValueTask AcceptAsync(CancellationToken cancellationToken = default)
+ {
+ while (await _acceptQueue.Reader.WaitToReadAsync())
+ while (_acceptQueue.Reader.TryRead(out var connection))
+ return connection;
+
+ return null;
+ }
+
+ ///
+ /// Aborts all unaccepted connections in the queue
+ ///
+ ///
+ public async Task AbortQueuedConnectionAsync()
+ {
+ while (await _acceptQueue.Reader.WaitToReadAsync())
+ while (_acceptQueue.Reader.TryRead(out var connection))
+ // REVIEW: Pass an abort reason?
+ connection.Abort();
+ }
+
+ ///
+ /// Creates a socket which can be used to accept an incoming connection.
+ ///
+ protected UvStreamHandle CreateAcceptSocket()
+ {
+ switch (EndPoint)
+ {
+ case IPEndPoint _:
+ return AcceptTcp();
+ case UnixDomainSocketEndPoint _:
+ return AcceptPipe();
+ case FileHandleEndPoint _:
+ return AcceptHandle();
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ protected internal void HandleConnection(UvStreamHandle socket)
+ {
+ try
+ {
+ IPEndPoint remoteEndPoint = null;
+ IPEndPoint localEndPoint = null;
+
+ if (socket is UvTcpHandle tcpHandle)
+ try
+ {
+ remoteEndPoint = tcpHandle.GetPeerIPEndPoint();
+ localEndPoint = tcpHandle.GetSockIPEndPoint();
+ }
+ catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode))
+ {
+ TransportContext.Log.ConnectionReset("(null)");
+ socket.Dispose();
+ return;
+ }
+
+ var options = TransportContext.Options;
+ var connection = new LibuvConnection(socket, TransportContext.Log, Thread, remoteEndPoint, localEndPoint, InputOptions, OutputOptions, options.MaxReadBufferSize, options.MaxWriteBufferSize);
+ connection.Start();
+
+ bool accepted = _acceptQueue.Writer.TryWrite(connection);
+ Debug.Assert(accepted, "The connection was not written to the channel!");
+ }
+ catch (Exception ex)
+ {
+ TransportContext.Log.LogCritical(ex, $"Unexpected exception in {nameof(ListenerContext)}.{nameof(HandleConnection)}.");
+ }
+ }
+
+ private UvTcpHandle AcceptTcp()
+ {
+ var socket = new UvTcpHandle(TransportContext.Log);
+
+ try
+ {
+ socket.Init(Thread.Loop, Thread.QueueCloseHandle);
+ socket.NoDelay(true);
+ }
+ catch
+ {
+ socket.Dispose();
+ throw;
+ }
+
+ return socket;
+ }
+
+ private UvPipeHandle AcceptPipe()
+ {
+ var pipe = new UvPipeHandle(TransportContext.Log);
+
+ try
+ {
+ pipe.Init(Thread.Loop, Thread.QueueCloseHandle);
+ }
+ catch
+ {
+ pipe.Dispose();
+ throw;
+ }
+
+ return pipe;
+ }
+
+ protected void StopAcceptingConnections()
+ {
+ _acceptQueue.Writer.TryComplete();
+ }
+
+ private UvStreamHandle AcceptHandle()
+ {
+ var fileHandleEndPoint = (FileHandleEndPoint)EndPoint;
+
+ switch (fileHandleEndPoint.FileHandleType)
+ {
+ case FileHandleType.Auto:
+ throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first.");
+ case FileHandleType.Tcp:
+ return AcceptTcp();
+ case FileHandleType.Pipe:
+ return AcceptPipe();
+ default:
+ throw new NotSupportedException();
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/ListenerPrimary.cs b/Projects/Server/LibUv/ListenerPrimary.cs
new file mode 100644
index 000000000..da006b7fe
--- /dev/null
+++ b/Projects/Server/LibUv/ListenerPrimary.cs
@@ -0,0 +1,265 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.Extensions.Logging;
+using Server;
+
+#pragma warning disable 169
+
+namespace Libuv
+{
+ ///
+ /// A primary listener waits for incoming connections on a specified socket. Incoming
+ /// connections may be passed to a secondary listener to handle.
+ ///
+ public class ListenerPrimary : Listener
+ {
+ // The list of pipes that can be dispatched to (where we've confirmed the _pipeMessage)
+ private readonly List _dispatchPipes = new List();
+ // The list of pipes we've created but may not be part of _dispatchPipes
+ private readonly List _createdPipes = new List();
+ private int _dispatchIndex;
+ private string _pipeName;
+ private byte[] _pipeMessage;
+ private IntPtr _fileCompletionInfoPtr;
+ private bool _tryDetachFromIOCP = Core.IsWindows;
+
+ // this message is passed to write2 because it must be non-zero-length,
+ // but it has no other functional significance
+ private readonly ArraySegment> _dummyMessage = new ArraySegment>(new[] { new ArraySegment(new byte[] { 1, 2, 3, 4 }) });
+
+ public ListenerPrimary(LibuvTransportContext transportContext) : base(transportContext)
+ {
+ }
+
+ ///
+ /// For testing purposes.
+ ///
+ public int UvPipeCount => _dispatchPipes.Count;
+
+ private UvPipeHandle ListenPipe { get; set; }
+
+ public async Task StartAsync(
+ string pipeName,
+ byte[] pipeMessage,
+ EndPoint endPoint,
+ LibuvThread thread)
+ {
+ _pipeName = pipeName;
+ _pipeMessage = pipeMessage;
+
+ if (_fileCompletionInfoPtr == IntPtr.Zero)
+ {
+ var fileCompletionInfo = new FILE_COMPLETION_INFORMATION { Key = IntPtr.Zero, Port = IntPtr.Zero };
+ _fileCompletionInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(fileCompletionInfo));
+ Marshal.StructureToPtr(fileCompletionInfo, _fileCompletionInfoPtr, false);
+ }
+
+ await StartAsync(endPoint, thread).ConfigureAwait(false);
+
+ await Thread.PostAsync(listener => listener.PostCallback(), this).ConfigureAwait(false);
+ }
+
+ private void PostCallback()
+ {
+ ListenPipe = new UvPipeHandle(Log);
+ ListenPipe.Init(Thread.Loop, Thread.QueueCloseHandle, false);
+ ListenPipe.Bind(_pipeName);
+ ListenPipe.Listen(TransportContext.Options.Backlog,
+ (pipe, status, error, state) => ((ListenerPrimary)state).OnListenPipe(pipe, status, error), this);
+ }
+
+ private void OnListenPipe(UvStreamHandle pipe, int status, UvException error)
+ {
+ if (status < 0) return;
+
+ var dispatchPipe = new UvPipeHandle(Log);
+ // Add to the list of created pipes for disposal tracking
+ _createdPipes.Add(dispatchPipe);
+
+ try
+ {
+ dispatchPipe.Init(Thread.Loop, Thread.QueueCloseHandle, true);
+ pipe.Accept(dispatchPipe);
+
+ // Ensure client sends _pipeMessage before adding pipe to _dispatchPipes.
+ var readContext = new PipeReadContext(this);
+ dispatchPipe.ReadStart(
+ (handle, status2, state) => ((PipeReadContext)state).AllocCallback(handle, status2),
+ (handle, status2, state) => ((PipeReadContext)state).ReadCallback(handle, status2),
+ readContext);
+ }
+ catch (UvException ex)
+ {
+ dispatchPipe.Dispose();
+ Log.LogError(0, ex, "ListenerPrimary.OnListenPipe");
+ }
+ }
+
+ protected override void DispatchConnection(UvStreamHandle socket)
+ {
+ var index = _dispatchIndex++ % (_dispatchPipes.Count + 1);
+ if (index == _dispatchPipes.Count)
+ {
+ base.DispatchConnection(socket);
+ }
+ else
+ {
+ DetachFromIOCP(socket);
+ var dispatchPipe = _dispatchPipes[index];
+ var write = new UvWriteReq(Log);
+ try
+ {
+ write.Init(Thread);
+ write.Write2(
+ dispatchPipe,
+ _dummyMessage,
+ socket,
+ (write2, status, error, state) =>
+ {
+ write2.Dispose();
+ ((UvStreamHandle)state).Dispose();
+ },
+ socket);
+ }
+ catch (UvException)
+ {
+ write.Dispose();
+ throw;
+ }
+ }
+ }
+
+ private void DetachFromIOCP(UvHandle handle)
+ {
+ if (!_tryDetachFromIOCP) return;
+
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff728840(v=vs.85).aspx
+ const int FileReplaceCompletionInformation = 61;
+ // https://msdn.microsoft.com/en-us/library/cc704588.aspx
+ const uint STATUS_INVALID_INFO_CLASS = 0xC0000003;
+
+ var statusBlock = new IO_STATUS_BLOCK();
+ var socket = IntPtr.Zero;
+ Thread.Loop.Libuv.uv_fileno(handle, ref socket);
+
+ if (NtSetInformationFile(socket, out statusBlock, _fileCompletionInfoPtr,
+ (uint)Marshal.SizeOf(), FileReplaceCompletionInformation) == STATUS_INVALID_INFO_CLASS)
+ // Replacing IOCP information is only supported on Windows 8.1 or newer
+ _tryDetachFromIOCP = false;
+ }
+
+ private struct IO_STATUS_BLOCK
+ {
+ uint status;
+ ulong information;
+ }
+
+ private struct FILE_COMPLETION_INFORMATION
+ {
+ public IntPtr Port;
+ public IntPtr Key;
+ }
+
+ [DllImport("NtDll.dll")]
+ private static extern uint NtSetInformationFile(IntPtr FileHandle,
+ out IO_STATUS_BLOCK IoStatusBlock, IntPtr FileInformation, uint Length,
+ int FileInformationClass);
+
+ public override async Task DisposeAsync()
+ {
+ // Call base first so the ListenSocket gets closed and doesn't
+ // try to dispatch connections to closed pipes.
+ await base.DisposeAsync().ConfigureAwait(false);
+
+ if (_fileCompletionInfoPtr != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(_fileCompletionInfoPtr);
+ _fileCompletionInfoPtr = IntPtr.Zero;
+ }
+
+ if (Thread.FatalError == null && ListenPipe != null)
+ await Thread.PostAsync(listener =>
+ {
+ listener.ListenPipe.Dispose();
+
+ foreach (var pipe in listener._createdPipes) pipe.Dispose();
+ }, this).ConfigureAwait(false);
+ }
+
+ private class PipeReadContext
+ {
+ private const int _bufferLength = 16;
+
+ private readonly ListenerPrimary _listener;
+ private readonly byte[] _buf = new byte[_bufferLength];
+ private readonly IntPtr _bufPtr;
+ private GCHandle _bufHandle;
+ private int _bytesRead;
+
+ public PipeReadContext(ListenerPrimary listener)
+ {
+ _listener = listener;
+ _bufHandle = GCHandle.Alloc(_buf, GCHandleType.Pinned);
+ _bufPtr = _bufHandle.AddrOfPinnedObject();
+ }
+
+ public LibuvFunctions.uv_buf_t AllocCallback(UvStreamHandle dispatchPipe, int suggestedSize) => dispatchPipe.Libuv.buf_init(_bufPtr + _bytesRead, _bufferLength - _bytesRead);
+
+ public void ReadCallback(UvStreamHandle dispatchPipe, int status)
+ {
+ if (status == LibuvConstants.EOF && _bytesRead == 0)
+ {
+ // This is an unexpected immediate termination of the dispatch pipe most likely caused by an
+ // external process scanning the pipe, so don't we don't log it too severely.
+ // https://github.com/aspnet/AspNetCore/issues/4741
+
+ dispatchPipe.Dispose();
+ _bufHandle.Free();
+ _listener.Log.LogDebug("An internal pipe was opened unexpectedly.");
+ return;
+ }
+
+ try
+ {
+ dispatchPipe.Libuv.ThrowIfErrored(status);
+
+ _bytesRead += status;
+
+ if (_bytesRead == _bufferLength)
+ {
+ var correctMessage = true;
+
+ for (var i = 0; i < _bufferLength; i++)
+ if (_buf[i] != _listener._pipeMessage[i])
+ correctMessage = false;
+
+ if (correctMessage)
+ {
+ _listener._dispatchPipes.Add((UvPipeHandle)dispatchPipe);
+ dispatchPipe.ReadStop();
+ _bufHandle.Free();
+ }
+ else
+ {
+ throw new IOException("Bad data sent over an internal pipe.");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ dispatchPipe.Dispose();
+ _bufHandle.Free();
+ _listener.Log.LogError(0, ex, "ListenerPrimary.ReadCallback");
+ }
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/ListenerSecondary.cs b/Projects/Server/LibUv/ListenerSecondary.cs
new file mode 100644
index 000000000..4377a4e9c
--- /dev/null
+++ b/Projects/Server/LibUv/ListenerSecondary.cs
@@ -0,0 +1,192 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Libuv.Internal;
+using Microsoft.Extensions.Logging;
+
+namespace Libuv
+{
+ ///
+ /// A secondary listener is delegated requests from a primary listener via a named pipe or
+ /// UNIX domain socket.
+ ///
+ public class ListenerSecondary : ListenerContext, IAsyncDisposable
+ {
+ private string _pipeName;
+ private byte[] _pipeMessage;
+ private IntPtr _ptr;
+ private LibuvFunctions.uv_buf_t _buf;
+ private bool _closed;
+
+ public ListenerSecondary(LibuvTransportContext transportContext) : base(transportContext) => _ptr = Marshal.AllocHGlobal(4);
+
+ UvPipeHandle DispatchPipe { get; set; }
+
+ public ILibuvTrace Log => TransportContext.Log;
+
+ public Task StartAsync(
+ string pipeName,
+ byte[] pipeMessage,
+ EndPoint endPoint,
+ LibuvThread thread)
+ {
+ _pipeName = pipeName;
+ _pipeMessage = pipeMessage;
+ _buf = thread.Loop.Libuv.buf_init(_ptr, 4);
+
+ EndPoint = endPoint;
+ Thread = thread;
+ DispatchPipe = new UvPipeHandle(Log);
+
+ var tcs = new TaskCompletionSource(this, TaskCreationOptions.RunContinuationsAsynchronously);
+ Thread.Post(StartCallback, tcs);
+ return tcs.Task;
+ }
+
+ private static void StartCallback(TaskCompletionSource tcs)
+ {
+ var listener = (ListenerSecondary)tcs.Task.AsyncState;
+ listener.StartedCallback(tcs);
+ }
+
+ private void StartedCallback(TaskCompletionSource tcs)
+ {
+ var connect = new UvConnectRequest(Log);
+ try
+ {
+ DispatchPipe.Init(Thread.Loop, Thread.QueueCloseHandle, true);
+ connect.Init(Thread);
+ connect.Connect(
+ DispatchPipe,
+ _pipeName,
+ (connect2, status, error, state) => ConnectCallback(connect2, status, error, (TaskCompletionSource)state),
+ tcs);
+ }
+ catch (Exception ex)
+ {
+ DispatchPipe.Dispose();
+ connect.Dispose();
+ tcs.SetException(ex);
+ }
+ }
+
+ private static void ConnectCallback(UvConnectRequest connect, int status, UvException error, TaskCompletionSource tcs)
+ {
+ var listener = (ListenerSecondary)tcs.Task.AsyncState;
+ _ = listener.ConnectedCallback(connect, status, error, tcs);
+ }
+
+ private async Task ConnectedCallback(UvConnectRequest connect, int status, UvException error, TaskCompletionSource tcs)
+ {
+ connect.Dispose();
+ if (error != null)
+ {
+ tcs.SetException(error);
+ return;
+ }
+
+ var writeReq = new UvWriteReq(Log);
+
+ try
+ {
+ DispatchPipe.ReadStart(
+ (handle, status2, state) => ((ListenerSecondary)state)._buf,
+ (handle, status2, state) => ((ListenerSecondary)state).ReadStartCallback(handle, status2),
+ this);
+
+ writeReq.Init(Thread);
+ var result = await writeReq.WriteAsync(
+ DispatchPipe,
+ new ArraySegment>(new[] { new ArraySegment(_pipeMessage) }));
+
+ if (result.Error != null)
+ tcs.SetException(result.Error);
+ else
+ tcs.SetResult(0);
+ }
+ catch (Exception ex)
+ {
+ DispatchPipe.Dispose();
+ tcs.SetException(ex);
+ }
+ finally
+ {
+ writeReq.Dispose();
+ }
+ }
+
+ private void ReadStartCallback(UvStreamHandle handle, int status)
+ {
+ if (status < 0)
+ {
+ if (status != LibuvConstants.EOF)
+ {
+ Thread.Loop.Libuv.Check(status, out var ex);
+ Log.LogError(0, ex, "DispatchPipe.ReadStart");
+ }
+
+ DispatchPipe.Dispose();
+ return;
+ }
+
+ if (_closed || DispatchPipe.PendingCount() == 0) return;
+
+ var acceptSocket = CreateAcceptSocket();
+
+ try
+ {
+ DispatchPipe.Accept(acceptSocket);
+
+ HandleConnection(acceptSocket);
+ }
+ catch (UvException ex) when (LibuvConstants.IsConnectionReset(ex.StatusCode))
+ {
+ Log.ConnectionReset("(null)");
+ acceptSocket.Dispose();
+ }
+ catch (UvException ex)
+ {
+ Log.LogError(0, ex, "DispatchPipe.Accept");
+ acceptSocket.Dispose();
+ }
+ }
+
+ private void FreeBuffer()
+ {
+ var ptr = Interlocked.Exchange(ref _ptr, IntPtr.Zero);
+ if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr);
+ }
+
+ public async Task DisposeAsync()
+ {
+ // Ensure the event loop is still running.
+ // If the event loop isn't running and we try to wait on this Post
+ // to complete, then LibuvTransport will never be disposed and
+ // the exception that stopped the event loop will never be surfaced.
+ if (Thread.FatalError == null)
+ {
+ await Thread.PostAsync(listener =>
+ {
+ listener.DispatchPipe.Dispose();
+ listener.FreeBuffer();
+
+ listener._closed = true;
+
+ listener.StopAcceptingConnections();
+
+ }, this).ConfigureAwait(false);
+ }
+ else
+ {
+ FreeBuffer();
+
+ StopAcceptingConnections();
+ }
+ }
+ }
+}
diff --git a/Projects/Server/LibUv/WriteReqPool.cs b/Projects/Server/LibUv/WriteReqPool.cs
new file mode 100644
index 000000000..745874344
--- /dev/null
+++ b/Projects/Server/LibUv/WriteReqPool.cs
@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Libuv.Internal;
+
+namespace Libuv
+{
+ public class WriteReqPool
+ {
+ private const int _maxPooledWriteReqs = 1024;
+
+ private readonly LibuvThread _thread;
+ private readonly Queue _pool = new Queue(_maxPooledWriteReqs);
+ private readonly ILibuvTrace _log;
+ private bool _disposed;
+
+ public WriteReqPool(LibuvThread thread, ILibuvTrace log)
+ {
+ _thread = thread;
+ _log = log;
+ }
+
+ public UvWriteReq Allocate()
+ {
+ if (_disposed) throw new ObjectDisposedException(GetType().Name);
+
+ UvWriteReq req;
+ if (_pool.Count > 0)
+ {
+ req = _pool.Dequeue();
+ }
+ else
+ {
+ req = new UvWriteReq(_log);
+ req.Init(_thread);
+ }
+
+ return req;
+ }
+
+ public void Return(UvWriteReq req)
+ {
+ if (_disposed) throw new ObjectDisposedException(GetType().Name);
+
+ if (_pool.Count < _maxPooledWriteReqs)
+ _pool.Enqueue(req);
+ else
+ req.Dispose();
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ while (_pool.Count > 0) _pool.Dequeue().Dispose();
+ }
+ }
+ }
+}
diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index 53a975885..bb1b29bc9 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -143,9 +143,13 @@ namespace Server
public static int ProcessorCount{ get; private set; }
- public static bool Unix{ get; private set; }
+ public static bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ public static bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ public static bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
+ public static bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD;
+ public static bool Unix = IsDarwin || IsFreeBSD || IsLinux;
- public static string ExePath => m_ExePath ?? (m_ExePath = Assembly.Location);
+ public static string ExePath => m_ExePath ??= Assembly.Location;
public static string BaseDirectory
{
@@ -261,8 +265,9 @@ namespace Server
{
try
{
- foreach (Listener l in MessagePump.Listeners)
- l.Dispose();
+ Task.WhenAll(
+ MessagePump.Listeners.Select(listener => listener.Dispose())
+ ).Wait();
}
catch
{
@@ -413,14 +418,7 @@ namespace Server
Console.WriteLine("Core: Optimizing for {0} {2}processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s",
Is64Bit ? "64-bit " : "");
- int platform = (int)Environment.OSVersion.Platform;
- if (platform == 4 || platform == 128)
- {
- // MS 4, MONO 128
- Unix = true;
- Console.WriteLine("Core: Unix environment detected");
- }
- else
+ if (IsWindows)
{
m_ConsoleEventHandler = OnConsoleEvent;
UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true);
diff --git a/Projects/Server/Mobile.cs b/Projects/Server/Mobile.cs
index 0fe1f420e..5a000073c 100644
--- a/Projects/Server/Mobile.cs
+++ b/Projects/Server/Mobile.cs
@@ -1445,7 +1445,7 @@ namespace Server
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)]
public NetState NetState
{
- get => m_NetState?.Socket != null && !m_NetState.IsDisposing ? m_NetState : null;
+ get => m_NetState?.Connection != null && !m_NetState.IsDisposing ? m_NetState : null;
set
{
if (m_NetState != value)
diff --git a/Projects/Server/Network/Listener.cs b/Projects/Server/Network/Listener.cs
index 2146f9e16..f0b929748 100644
--- a/Projects/Server/Network/Listener.cs
+++ b/Projects/Server/Network/Listener.cs
@@ -24,53 +24,54 @@ using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
+using Libuv;
+using Libuv.Internal;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Logging;
namespace Server.Network
{
public class Listener
{
- private Socket m_Socket;
- private IPEndPoint m_EndPoint;
+ private static readonly LibuvFunctions functions = new LibuvFunctions();
+
+ private readonly IPEndPoint m_EndPoint;
+ private LibuvConnectionListener m_Listener;
+
public Listener(IPEndPoint ipep)
{
-#pragma warning disable IDE0068 // Use recommended dispose pattern
- m_Socket = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-#pragma warning restore IDE0068 // Use recommended dispose pattern
-
- m_Socket.LingerState.Enabled = false;
- m_Socket.ExclusiveAddressUse = false;
m_EndPoint = ipep;
+ LibuvTransportContext transport = new LibuvTransportContext
+ {
+ Options = new LibuvTransportOptions(),
+ AppLifetime = new ApplicationLifetime(
+ LoggerFactory.Create(builder => { builder.AddConsole(); }).CreateLogger()
+ ),
+ Log = new LibuvTrace(LoggerFactory.Create(builder => { builder.AddConsole(); }).CreateLogger("network"))
+ };
+
+ m_Listener = new LibuvConnectionListener(functions, transport, ipep);
}
public virtual async Task Start(MessagePump pump)
{
try
{
- m_Socket.Bind(m_EndPoint);
- m_Socket.Listen(8);
+ await m_Listener.BindAsync();
+ }
+ catch (AddressInUseException)
+ {
+ Console.WriteLine("Listener Failed: {0}:{1} (In Use)", m_EndPoint.Address, m_EndPoint.Port);
+ m_Listener = null;
+ return;
}
catch (Exception e)
{
- if (e is SocketException se)
- {
- if (se.ErrorCode == 10048)
- {
- // WSAEADDRINUSE
- Console.WriteLine("Listener Failed: {0}:{1} (In Use)", m_EndPoint.Address, m_EndPoint.Port);
- }
- else if (se.ErrorCode == 10049)
- {
- // WSAEADDRNOTAVAIL
- Console.WriteLine("Listener Failed: {0}:{1} (Unavailable)", m_EndPoint.Address, m_EndPoint.Port);
- }
- else
- {
- Console.WriteLine("Listener Exception:");
- Console.WriteLine(e);
- }
- }
+ Console.WriteLine("Listener Exception:");
+ Console.WriteLine(e);
- m_Socket = null;
+ m_Listener = null;
return;
}
@@ -78,10 +79,10 @@ namespace Server.Network
while (true)
{
- Socket s;
+ ConnectionContext context;
try
{
- s = await m_Socket.AcceptAsync().ConfigureAwait(false);
+ context = await m_Listener.AcceptAsync();
}
catch (SocketException ex)
{
@@ -89,16 +90,16 @@ namespace Server.Network
continue;
}
- if (VerifySocket(s))
- _ = new NetState(s, pump);
+ if (VerifySocket(context))
+ _ = new NetState(context, pump);
else
- Release(s);
+ Release(context);
}
}
private void DisplayListener()
{
- if (!(m_Socket.LocalEndPoint is IPEndPoint ipep))
+ if (!(m_Listener.EndPoint is IPEndPoint ipep))
return;
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
@@ -113,16 +114,14 @@ namespace Server.Network
}
}
else
- {
Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port);
- }
}
- private bool VerifySocket(Socket socket)
+ private static bool VerifySocket(ConnectionContext context)
{
try
{
- SocketConnectEventArgs args = new SocketConnectEventArgs(socket);
+ SocketConnectEventArgs args = new SocketConnectEventArgs(context);
EventSink.InvokeSocketConnect(args);
@@ -131,44 +130,47 @@ namespace Server.Network
catch (Exception ex)
{
NetState.TraceException(ex);
-
return false;
}
}
- private void Release(Socket socket)
+ private static void Release(ConnectionContext context)
{
try
{
- socket.Shutdown(SocketShutdown.Both);
+ context.Abort(new ConnectionAbortedException("Failed socket verification."));
}
- catch (SocketException ex)
+ catch (Exception ex)
{
NetState.TraceException(ex);
}
try
{
- socket.Close();
+ // TODO: Is this needed?
+ context.DisposeAsync();
}
- catch (SocketException ex)
- {
- NetState.TraceException(ex);
- }
-
- try
- {
- socket.Dispose();
- }
- catch (SocketException ex)
+ catch (Exception ex)
{
NetState.TraceException(ex);
}
}
- public void Dispose()
+ public async Task Dispose()
{
- Interlocked.Exchange(ref m_Socket, null)?.Close();
+ LibuvConnectionListener listener = Interlocked.Exchange(ref m_Listener, null);
+ if (listener != null)
+ try
+ {
+ await listener.UnbindAsync();
+ await listener.DisposeAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Listener: Failed to dispose.");
+ Console.WriteLine(ex);
+ }
+
GC.SuppressFinalize(this);
}
}
diff --git a/Projects/Server/Network/MessagePump.cs b/Projects/Server/Network/MessagePump.cs
index 0919630d9..3904d73aa 100644
--- a/Projects/Server/Network/MessagePump.cs
+++ b/Projects/Server/Network/MessagePump.cs
@@ -23,6 +23,8 @@ using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Net;
+using System.Threading.Tasks;
+using SignalR;
namespace Server.Network
{
@@ -40,9 +42,9 @@ namespace Server.Network
listeners[Listeners.Length] = listener;
}
- public void QueueWork(NetState ns, in ReadOnlySequence seq, OnPacketReceive onReceive)
+ public void QueueWork(NetState ns, IMemoryOwner memOwner, OnPacketReceive onReceive)
{
- m_WorkQueue.Enqueue(new Work(ns, seq, onReceive));
+ m_WorkQueue.Enqueue(new Work(ns, memOwner, onReceive));
Core.Set();
}
@@ -54,21 +56,22 @@ namespace Server.Network
if (!m_WorkQueue.TryDequeue(out Work work))
break;
- work.OnReceive(work.State, new PacketReader(work.Sequence));
+ work.OnReceive(work.State, new PacketReader(new ReadOnlySequence(work.MemoryOwner.Memory)));
+ work.MemoryOwner.Dispose();
}
}
- // TODO: Optimize this with a pool
private class Work
{
- public NetState State;
- public ReadOnlySequence Sequence;
- public OnPacketReceive OnReceive;
+ public readonly NetState State;
+ // TODO: Force dispose?
+ public readonly IMemoryOwner MemoryOwner;
+ public readonly OnPacketReceive OnReceive;
- public Work(NetState ns, in ReadOnlySequence seq, OnPacketReceive onReceive)
+ public Work(NetState ns, IMemoryOwner memOwner, OnPacketReceive onReceive)
{
State = ns;
- Sequence = seq;
+ MemoryOwner = memOwner;
OnReceive = onReceive;
}
}
diff --git a/Projects/Server/Network/NetState.cs b/Projects/Server/Network/NetState.cs
index 14355146d..290f6a6e9 100644
--- a/Projects/Server/Network/NetState.cs
+++ b/Projects/Server/Network/NetState.cs
@@ -28,6 +28,7 @@ using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Connections;
using Server.Accounting;
using Server.Diagnostics;
using Server.Gumps;
@@ -93,7 +94,6 @@ namespace Server.Network
{
private string m_ToString;
private ClientVersion m_Version;
- private SendQueue m_SendQueue = new SendQueue();
public DateTime ConnectedOn { get; }
@@ -266,11 +266,9 @@ namespace Server.Network
return newTrade.From.Container;
}
- public bool Running { get; private set; }
-
public bool Seeded { get; set; }
- public Socket Socket { get; private set; }
+ public ConnectionContext Connection { get; private set; }
public bool CompressionEnabled { get; set; }
@@ -282,12 +280,16 @@ namespace Server.Network
public List Menus { get; private set; }
+ public PipeReader RecvPipe => Connection.Transport.Input;
+ public PipeWriter SendPipe => Connection.Transport.Output;
+
public static int GumpCap { get; set; } = 512;
public static int HuePickerCap { get; set; } = 512;
public static int MenuCap { get; set; } = 512;
+
public void WriteConsole(string text)
{
Console.WriteLine("Client: {0}: {1}", this, text);
@@ -403,23 +405,24 @@ namespace Server.Network
public static List Instances { get; } = new List();
- public NetState(Socket socket, MessagePump pump)
+ public void SetConnectionAlive() => m_NextCheckActivity = Core.TickCount + 60000;
+
+ public NetState(ConnectionContext connection, MessagePump pump)
{
- Socket = socket;
+ Connection = connection;
Seeded = false;
- Running = false;
Gumps = new List();
HuePickers = new List();
Menus = new List();
Trades = new List();
- m_NextCheckActivity = Core.TickCount + 30000;
+ SetConnectionAlive();
Instances.Add(this);
try
{
- Address = Utility.Intern(((IPEndPoint)Socket.RemoteEndPoint).Address);
+ Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address);
m_ToString = Address.ToString();
}
catch (Exception ex)
@@ -430,10 +433,11 @@ namespace Server.Network
}
ConnectedOn = DateTime.UtcNow;
- _ = Start(pump);
+ Console.WriteLine("Client: {0}: Connected. [{1} Online]", this, Instances.Count);
+
+ _ = ProcessRecvs(pump);
CreatedCallback?.Invoke(this);
- Console.WriteLine("Client: {0}: Connected. [{1} Online]", this, NetState.Instances.Count);
}
public static void Pause()
@@ -448,13 +452,38 @@ namespace Server.Network
public virtual void Send(Packet p)
{
- if (Socket == null || BlockAllPackets)
+ if (Connection == null || BlockAllPackets)
{
p.OnSend();
return;
}
- m_SendQueue.Enqueue(p);
+ try
+ {
+ ReadOnlySpan buffer = p.Compile(CompressionEnabled, out int length);
+
+ if (buffer.Length <= 0 || length <= 0)
+ {
+ p.OnSend();
+ return;
+ }
+
+ buffer.Slice(0, length).CopyTo(SendPipe.GetSpan(length));
+ SendPipe.Advance(length);
+ SendPipe.FlushAsync().GetAwaiter().GetResult();
+
+ p.OnSend();
+ }
+ catch (SocketException ex)
+ {
+ Console.WriteLine(ex);
+ TraceException(ex);
+ Dispose();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
+ }
}
public bool CheckEncrypted(int packetID)
@@ -470,121 +499,31 @@ namespace Server.Network
return false;
}
- private Pipe m_RecvdPipe;
-
- private async Task Start(MessagePump pump)
+ private async Task ProcessRecvs(MessagePump pump)
{
- m_RecvdPipe = new Pipe();
- Running = true;
-
- await Task.WhenAll(ProcessSends(), ProcessRecvs(), HandlePackets(pump)).ConfigureAwait(false);
- }
-
- private async Task ProcessRecvs()
- {
- PipeWriter w = m_RecvdPipe.Writer;
-
while (true)
{
- if (m_AsyncState.Paused)
- {
- await Timer.Pause(50).ConfigureAwait(false);
- continue;
- }
-
- try
- {
- Memory memory = w.GetMemory();
- int bytesRead = await Socket.ReceiveAsync(memory, SocketFlags.None).ConfigureAwait(false);
- if (bytesRead == 0)
- break;
-
- Interlocked.Exchange(ref m_NextCheckActivity, Core.TickCount + 90000);
-
- w.Advance(bytesRead);
-
- FlushResult result = await w.FlushAsync().ConfigureAwait(false);
- if (result.IsCompleted || result.IsCanceled)
- break;
- }
- catch
- {
- break;
- }
- }
-
- w.Complete();
- Dispose();
- }
-
- private async Task ProcessSends()
- {
- while (true)
- try
- {
- Packet p = await m_SendQueue?.DequeueAsync();
- if (p == null)
- break;
-
- ReadOnlyMemory buffer = p.Compile(CompressionEnabled, out int length);
-
- if (buffer.Length <= 0 || length <= 0)
- {
- p.OnSend();
- return;
- }
-
- buffer = buffer.Slice(0, length);
-
- PacketSendProfile prof = null;
-
- if (Core.Profiling)
- prof = PacketSendProfile.Acquire(p.GetType());
-
- prof?.Start();
-
- await Socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
-
- p.OnSend();
-
- prof?.Finish(length);
- }
- catch (SocketException ex)
- {
- Console.WriteLine(ex);
- TraceException(ex);
- Dispose();
- break;
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- }
-
- private async Task HandlePackets(MessagePump pump)
- {
- PipeReader pr = m_RecvdPipe.Reader;
-
- while (true)
- {
- ReadResult result = await pr.ReadAsync();
+ ReadResult result = await RecvPipe.ReadAsync();
ReadOnlySequence seq = result.Buffer;
- if (seq.Length == 0)
+
+ if (seq.IsEmpty)
break;
- long pos = PacketHandlers.ProcessPacket(pump, this, seq);
+ SetConnectionAlive();
+
+ int pos = PacketHandlers.ProcessPacket(pump, this, seq);
if (pos <= 0)
break;
- pr.AdvanceTo(seq.GetPosition(pos, seq.Start));
+ RecvPipe.AdvanceTo(seq.Slice(0, pos).End);
if (result.IsCompleted || result.IsCanceled)
break;
}
- pr.Complete();
+ RecvPipe.Complete();
+ Dispose();
}
public PacketHandler GetHandler(int packetID) =>
@@ -595,7 +534,7 @@ namespace Server.Network
public void CheckAlive(long curTicks)
{
- if (Socket == null || m_NextCheckActivity - curTicks >= 0)
+ if (Connection == null || m_NextCheckActivity - curTicks >= 0)
return;
Console.WriteLine("Client: {0}: Disconnecting due to inactivity...", this);
@@ -637,21 +576,18 @@ namespace Server.Network
if (disposing == 1)
return;
- Task.Run(async () =>
+ try
{
- m_RecvdPipe.Reader.CancelPendingRead();
- m_RecvdPipe.Reader.Complete();
- await m_RecvdPipe.Writer.FlushAsync().ConfigureAwait(false);
- m_RecvdPipe.Writer.Complete();
+ Connection.Abort();
+ Task.Run(Connection.DisposeAsync).Wait();
+ }
+ catch (Exception ex)
+ {
+ TraceException(ex);
+ }
- try { Socket.Shutdown(SocketShutdown.Both); } catch (Exception ex) { TraceException(ex); }
- try { Socket.Close(); } catch (Exception ex) { TraceException(ex); }
-
- Socket = null;
- m_RecvdPipe = null;
- m_SendQueue = null;
- m_Disposed.Enqueue(this);
- });
+ Connection = null;
+ m_Disposed.Enqueue(this);
}
public static void Initialize()
diff --git a/Projects/Server/Network/PacketHandler.cs b/Projects/Server/Network/PacketHandler.cs
index 022405fd5..3b87df425 100644
--- a/Projects/Server/Network/PacketHandler.cs
+++ b/Projects/Server/Network/PacketHandler.cs
@@ -28,7 +28,7 @@ namespace Server.Network
public class PacketHandler
{
- public PacketHandler(int packetID, long length, bool ingame, OnPacketReceive onReceive)
+ public PacketHandler(int packetID, int length, bool ingame, OnPacketReceive onReceive)
{
PacketID = packetID;
Length = length;
@@ -38,7 +38,7 @@ namespace Server.Network
public int PacketID{ get; }
- public long Length{ get; }
+ public int Length{ get; }
public OnPacketReceive OnReceive{ get; }
diff --git a/Projects/Server/Network/PacketHandlers.cs b/Projects/Server/Network/PacketHandlers.cs
index 37ec26672..beda676b3 100644
--- a/Projects/Server/Network/PacketHandlers.cs
+++ b/Projects/Server/Network/PacketHandlers.cs
@@ -109,43 +109,32 @@ namespace Server.Network
Register(0x01, 5, false, Disconnect);
Register(0x02, 7, true, MovementReq);
Register(0x03, 0, true, AsciiSpeech);
- Register(0x04, 2, true, GodModeRequest);
Register(0x05, 5, true, AttackReq);
Register(0x06, 5, true, UseReq);
Register(0x07, 7, true, LiftReq);
Register(0x08, 14, true, DropReq);
Register(0x09, 5, true, LookReq);
- Register(0x0A, 11, true, Edit);
Register(0x12, 0, true, TextCommand);
Register(0x13, 10, true, EquipReq);
- Register(0x14, 6, true, ChangeZ);
Register(0x22, 3, true, Resynchronize);
Register(0x2C, 2, true, DeathStatusResponse);
Register(0x34, 10, true, MobileQuery);
Register(0x3A, 0, true, ChangeSkillLock);
Register(0x3B, 0, true, VendorBuyReply);
- Register(0x47, 11, true, NewTerrain);
- Register(0x48, 73, true, NewAnimData);
- Register(0x58, 106, true, NewRegion);
Register(0x5D, 73, false, PlayCharacter);
- Register(0x61, 9, true, DeleteStatic);
Register(0x6C, 19, true, TargetResponse);
Register(0x6F, 0, true, SecureTrade);
Register(0x72, 5, true, SetWarMode);
Register(0x73, 2, false, PingReq);
Register(0x75, 35, true, RenameRequest);
- Register(0x79, 9, true, ResourceQuery);
- Register(0x7E, 2, true, GodviewQuery);
Register(0x7D, 13, true, MenuResponse);
Register(0x80, 62, false, AccountLogin);
Register(0x83, 39, false, DeleteCharacter);
Register(0x91, 65, false, GameLogin);
Register(0x95, 9, true, HuePickerResponse);
- Register(0x96, 0, true, GameCentralMoniter);
Register(0x98, 0, true, MobileNameRequest);
Register(0x9A, 0, true, AsciiPromptResponse);
Register(0x9B, 258, true, HelpRequest);
- Register(0x9D, 51, true, GMSingle);
Register(0x9F, 0, true, VendorSellReply);
Register(0xA0, 3, false, PlayServer);
Register(0xA4, 149, false, SystemInfo);
@@ -161,8 +150,6 @@ namespace Server.Network
Register(0xBF, 0, true, ExtendedCommand);
Register(0xC2, 0, true, UnicodePromptResponse);
Register(0xC8, 2, true, SetUpdateRange);
- Register(0xC9, 6, true, TripTime);
- Register(0xCA, 6, true, UTripTime);
Register(0xCF, 0, false, AccountLogin);
Register(0xD0, 0, true, ConfigurationFile);
Register(0xD1, 2, true, LogoutReq);
@@ -172,6 +159,7 @@ namespace Server.Network
Register(0xEF, 21, false, LoginServerSeed);
Register(0xF4, 0, false, CrashReport);
Register(0xF8, 106, false, CreateCharacter70160);
+ Register(0xFB, 2, false, ShowPublicHouseContent);
Register6017(0x08, 15, true, DropReq6017);
@@ -214,9 +202,7 @@ namespace Server.Network
public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive)
{
Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
-
- if (m_6017Handlers[packetID] == null)
- m_6017Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
+ m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive);
}
public static PacketHandler GetHandler(int packetID) => Handlers[packetID];
@@ -291,7 +277,9 @@ namespace Server.Network
ph.ThrottleCallback = t;
}
- public static long ProcessPacket(MessagePump pump, NetState ns, in ReadOnlySequence seq)
+ private static MemoryPool _memoryPool = SlabMemoryPoolFactory.Create();
+
+ public static int ProcessPacket(MessagePump pump, NetState ns, in ReadOnlySequence seq)
{
PacketReader r = new PacketReader(seq);
@@ -342,7 +330,7 @@ namespace Server.Network
return -1;
}
- long packetLength = handler.Length;
+ int packetLength = handler.Length;
if (handler.Length <= 0 && r.Length >= 3)
{
packetLength = r.ReadUInt16();
@@ -373,16 +361,12 @@ namespace Server.Network
if (throttled > TimeSpan.Zero)
ns.ThrottledUntil = DateTime.UtcNow + throttled;
- PacketReceiveProfile prof = null;
+ ReadOnlySequence packet = seq.Slice(r.Position);
+ IMemoryOwner