LibUv Networking (#65)

This commit is contained in:
Kamron Batman 2019-12-26 18:08:50 -08:00 committed by GitHub
parent ec6629fbe9
commit 5e2be2d7b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 5612 additions and 586 deletions

21
.gitignore vendored
View file

@ -8,6 +8,27 @@
/Distribution/rdrand.so /Distribution/rdrand.so
/Distribution/rdrand.dll /Distribution/rdrand.dll
/Distribution/zlib.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/obj
/Projects/Scripts/bin /Projects/Scripts/bin
/Projects/Server/obj /Projects/Server/obj

View file

@ -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). Start a discussion on the [repository issue tracker](https://github.com/modernuo/modernuo/issues).
## Reporting security issues and bugs ## 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. 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. 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. configurations, environment variables, dependencies, etc.
1. Increase the version numbers to the new version that this Pull Request would represent. 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/). 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. do not have permission to do that, you may request the second reviewer to merge it for you.
## Code of Conduct ## Code of Conduct

View file

@ -16,7 +16,7 @@ namespace Server
{ {
try try
{ {
IPAddress ip = ((IPEndPoint)e.Socket.RemoteEndPoint).Address; IPAddress ip = ((IPEndPoint)e.Context.RemoteEndPoint).Address;
if (Firewall.IsBlocked(ip)) if (Firewall.IsBlocked(ip))
{ {
@ -43,4 +43,4 @@ namespace Server
} }
} }
} }
} }

View file

@ -108,7 +108,7 @@ namespace Server.Misc
Timer.DelayCall(KickDelay, delegate Timer.DelayCall(KickDelay, delegate
{ {
if (state.Socket != null) if (state.Connection != null)
{ {
Console.WriteLine("Client: {0}: Disconnecting, bad version", state); Console.WriteLine("Client: {0}: Disconnecting, bad version", state);
state.Dispose(); state.Dispose();
@ -173,4 +173,4 @@ namespace Server.Misc
Kick Kick
} }
} }
} }

View file

@ -3,24 +3,25 @@ using System.IO;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using Microsoft.AspNetCore.Connections;
using Server.Network; using Server.Network;
namespace Server.Misc namespace Server.Misc
{ {
public class ServerList 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 * 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 * are private network addresses and AutoDetect is 'true' then RunUO will attempt to discover your public IP address
* for you automatically. * 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 * 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'. * Address set to 'null'.
* *
* If your public IP address cannot be determined, you must change the value of Address to your public IP address * 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 * 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. * 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 * 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 * 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. * 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 * 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 * 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. * 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 * 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. * 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 try
{ {
NetState ns = e.State; NetState ns = e.State;
Socket s = ns.Socket; ConnectionContext s = ns.Connection;
IPEndPoint ipep = (IPEndPoint)s.LocalEndPoint; IPEndPoint ipep = (IPEndPoint)s.LocalEndPoint;

View file

@ -5,8 +5,6 @@ namespace Server
{ {
public class SocketOptions public class SocketOptions
{ {
private const bool NagleEnabled = false; // Should the Nagle algorithm be enabled? This may reduce performance
private static IPEndPoint[] m_ListenerEndPoints = private static IPEndPoint[] m_ListenerEndPoints =
{ {
new IPEndPoint(IPAddress.Any, 2593) // Default: Listen on port 2593 on all IP addresses 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 // 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() public static void RegisterListeners()
{ {
for (int i = 0; i < m_ListenerEndPoints.Length; i++) for (int i = 0; i < m_ListenerEndPoints.Length; i++)
Core.MessagePump.AddListener(m_ListenerEndPoints[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
}
} }
} }

View file

@ -35,4 +35,7 @@
<IncludeInPackage>false</IncludeInPackage> <IncludeInPackage>false</IncludeInPackage>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.0.0" />
</ItemGroup>
</Project> </Project>

View file

@ -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)]; private static LocationInfo GetRandomDestination() => m_Destinations[Utility.Random(m_Destinations.Length)];

View file

@ -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
{
/// <summary>
/// Allows consumers to perform cleanup during a graceful shutdown.
/// </summary>
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<ApplicationLifetime> _logger;
public ApplicationLifetime(ILogger<ApplicationLifetime> logger) => _logger = logger;
/// <summary>
/// Triggered when the application host has fully started and is about to wait
/// for a graceful shutdown.
/// </summary>
public CancellationToken ApplicationStarted => _startedSource.Token;
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// Request may still be in flight. Shutdown will block until this event completes.
/// </summary>
public CancellationToken ApplicationStopping => _stoppingSource.Token;
/// <summary>
/// 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.
/// </summary>
public CancellationToken ApplicationStopped => _stoppedSource.Token;
/// <summary>
/// Signals the ApplicationStopping event and blocks until it completes.
/// </summary>
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);*/
}
}
}
/// <summary>
/// Signals the ApplicationStarted event and blocks until it completes.
/// </summary>
public void NotifyStarted()
{
try
{
ExecuteHandlers(_startedSource);
}
catch (Exception)
{
/* _logger.ApplicationError(LoggerEventIds.ApplicationStartupException,
"An error occurred starting the application",
ex);*/
}
}
/// <summary>
/// Signals the ApplicationStopped event and blocks until it completes.
/// </summary>
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);
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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
{
/// <summary>
/// Used to allocate and distribute re-usable blocks of memory.
/// </summary>
public class DiagnosticMemoryPool : MemoryPool<byte>
{
private readonly MemoryPool<byte> _pool;
private readonly bool _allowLateReturn;
private readonly bool _rentTracking;
private readonly object _syncObj;
private readonly HashSet<DiagnosticPoolBlock> _blocks;
private readonly List<Exception> _blockAccessExceptions;
private readonly TaskCompletionSource<object> _allBlocksReturned;
private int _totalBlocks;
/// <summary>
/// This default value passed in to Rent to use the default value for the pool.
/// </summary>
private const int AnySize = -1;
public DiagnosticMemoryPool(MemoryPool<byte> pool, bool allowLateReturn = false, bool rentTracking = false)
{
_pool = pool;
_allowLateReturn = allowLateReturn;
_rentTracking = rentTracking;
_blocks = new HashSet<DiagnosticPoolBlock>();
_syncObj = new object();
_allBlocksReturned = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
_blockAccessExceptions = new List<Exception>();
}
public bool IsDisposed { get; private set; }
public override IMemoryOwner<byte> 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;
}
}
}

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
public sealed class DiagnosticPoolBlock : MemoryManager<byte>
{
/// <summary>
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
/// </summary>
private readonly DiagnosticMemoryPool _pool;
private readonly IMemoryOwner<byte> _memoryOwner;
private MemoryHandle? _memoryHandle;
private Memory<byte> _memory;
private readonly object _syncObj = new object();
private bool _isDisposed;
private int _pinCount;
/// <summary>
/// This object cannot be instantiated outside of the static Create method
/// </summary>
internal DiagnosticPoolBlock(DiagnosticMemoryPool pool, IMemoryOwner<byte> memoryOwner)
{
_pool = pool;
_memoryOwner = memoryOwner;
_memory = memoryOwner.Memory;
}
public override Memory<byte> 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<byte> 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<byte> 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);
}
}
}

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
public sealed class MemoryPoolBlock : IMemoryOwner<byte>
{
private readonly int _offset;
private readonly int _length;
/// <summary>
/// This object cannot be instantiated outside of the static Create method
/// </summary>
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);
}
/// <summary>
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
/// </summary>
public SlabMemoryPool Pool { get; }
/// <summary>
/// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
/// </summary>
public MemoryPoolSlab Slab { get; }
public Memory<byte> Memory { get; }
~MemoryPoolBlock()
{
Pool.RefreshBlock(Slab, _offset, _length);
}
public void Dispose()
{
Pool.Return(this);
}
public void Lease()
{
}
}
}

View file

@ -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<byte> Create()
{
#if DEBUG
return new DiagnosticMemoryPool(CreateSlabMemoryPool());
#else
return CreateSlabMemoryPool();
#endif
}
public static MemoryPool<byte> CreateSlabMemoryPool() => new SlabMemoryPool();
}
}

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
public class MemoryPoolSlab : IDisposable
{
/// <summary>
/// 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.
/// </summary>
private GCHandle _gcHandle;
private bool _isDisposed;
public MemoryPoolSlab(byte[] data)
{
Array = data;
_gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
NativePointer = _gcHandle.AddrOfPinnedObject();
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
}

View file

@ -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
}
}
}

View file

@ -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
{
/// <summary>
/// Used to allocate and distribute re-usable blocks of memory.
/// </summary>
public sealed class SlabMemoryPool : MemoryPool<byte>
{
/// <summary>
/// The size of a block. 4096 is chosen because most operating systems use 4k pages.
/// </summary>
private const int _blockSize = 4096;
/// <summary>
/// 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.
/// </summary>
private const int _blockCount = 32;
/// <summary>
/// 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.
/// </summary>
public override int MaxBufferSize { get; } = _blockSize;
/// <summary>
/// The size of a block. 4096 is chosen because most operating systems use 4k pages.
/// </summary>
public static int BlockSize => _blockSize;
/// <summary>
/// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
/// </summary>
private static readonly int _slabLength = _blockSize * _blockCount;
/// <summary>
/// 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.
/// </summary>
private readonly ConcurrentQueue<MemoryPoolBlock> _blocks = new ConcurrentQueue<MemoryPoolBlock>();
/// <summary>
/// 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.
/// </summary>
private readonly ConcurrentStack<MemoryPoolSlab> _slabs = new ConcurrentStack<MemoryPoolSlab>();
/// <summary>
/// This is part of implementing the IDisposable pattern.
/// </summary>
private bool _isDisposed; // To detect redundant calls
private int _totalAllocatedBlocks;
private readonly object _disposeSync = new object();
/// <summary>
/// This default value passed in to Rent to use the default value for the pool.
/// </summary>
private const int AnySize = -1;
public override IMemoryOwner<byte> Rent(int size = AnySize)
{
if (size > _blockSize) MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize);
var block = Lease();
return block;
}
/// <summary>
/// Called to take a block from the pool.
/// </summary>
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
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);
}
}
}
}

View file

@ -1,22 +1,22 @@
/*************************************************************************** /*************************************************************************
* SpanWriter.cs * ModernUO *
* ------------------- * Copyright (C) 2019 - ModernUO Development Team *
* begin : August 5, 2019 * Email: hi@modernuo.com *
* copyright : (C) The ModernUO Team * File: SpanWriter.cs - Created: 2019/08/05 - Updated: 2019/12/24 *
* email : hi@modernuo.com * *
* * This program is free software: you can redistribute it and/or modify *
* $Id$ * 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 *
* This program is free software; you can redistribute it and/or modify * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* it under the terms of the GNU General Public License as published by * GNU General Public License for more details. *
* the Free Software Foundation; either version 2 of the License, or * *
* (at your option) any later version. * You should have received a copy of the GNU General Public License *
* * along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/ *************************************************************************/
using System; using System;
using System.Text; using System.Text;

View file

@ -22,6 +22,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using Microsoft.AspNetCore.Connections;
using Server.Accounting; using Server.Accounting;
using Server.Guilds; using Server.Guilds;
using Server.Network; using Server.Network;
@ -380,13 +381,13 @@ namespace Server
public class SocketConnectEventArgs : EventArgs public class SocketConnectEventArgs : EventArgs
{ {
public SocketConnectEventArgs(Socket s) public SocketConnectEventArgs(ConnectionContext c)
{ {
Socket = s; Context = c;
AllowConnection = true; AllowConnection = true;
} }
public Socket Socket{ get; } public ConnectionContext Context{ get; }
public bool AllowConnection{ get; set; } public bool AllowConnection{ get; set; }
} }

View file

@ -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];
});
}
}
}

View file

@ -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<byte> IMemoryPoolFeature.MemoryPool => MemoryPool;
IDuplexPipe IConnectionTransportFeature.Transport
{
get => Transport;
set => Transport = value;
}
IDictionary<object, object> 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()."));
}
}

View file

@ -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<KeyValuePair<Type, object>> 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<KeyValuePair<Type, object>>(2);
}
for (var i = 0; i < MaybeExtra.Count; i++)
{
if (MaybeExtra[i].Key == key)
{
MaybeExtra[i] = new KeyValuePair<Type, object>(key, value);
return;
}
}
MaybeExtra.Add(new KeyValuePair<Type, object>(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>()
{
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>(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<KeyValuePair<Type, object>> FastEnumerable()
{
if (_currentIConnectionIdFeature != null)
{
yield return new KeyValuePair<Type, object>(IConnectionIdFeatureType, _currentIConnectionIdFeature);
}
if (_currentIConnectionTransportFeature != null)
{
yield return new KeyValuePair<Type, object>(IConnectionTransportFeatureType, _currentIConnectionTransportFeature);
}
if (_currentIConnectionItemsFeature != null)
{
yield return new KeyValuePair<Type, object>(IConnectionItemsFeatureType, _currentIConnectionItemsFeature);
}
if (_currentIMemoryPoolFeature != null)
{
yield return new KeyValuePair<Type, object>(IMemoryPoolFeatureType, _currentIMemoryPoolFeature);
}
if (_currentIConnectionLifetimeFeature != null)
{
yield return new KeyValuePair<Type, object>(IConnectionLifetimeFeatureType, _currentIConnectionLifetimeFeature);
}
if (MaybeExtra != null)
{
foreach (var item in MaybeExtra)
{
yield return item;
}
}
}
IEnumerator<KeyValuePair<Type, object>> IEnumerable<KeyValuePair<Type, object>>.GetEnumerator() => FastEnumerable().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => FastEnumerable().GetEnumerator();
}
}

View file

@ -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<object, object> _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<byte> MemoryPool { get; }
public override IDuplexPipe Transport { get; set; }
public IDuplexPipe Application { get; set; }
public override IDictionary<object, object> 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();
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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<UvLoopHandle, int> _uv_loop_init;
public void loop_init(UvLoopHandle handle)
{
ThrowIfErrored(_uv_loop_init(handle));
}
public Func<IntPtr, int> _uv_loop_close;
public void loop_close(UvLoopHandle handle)
{
handle.Validate(true);
ThrowIfErrored(_uv_loop_close(handle.InternalGetHandle()));
}
public Func<UvLoopHandle, int, int> _uv_run;
public void run(UvLoopHandle handle, int mode)
{
handle.Validate();
ThrowIfErrored(_uv_run(handle, mode));
}
public Action<UvLoopHandle> _uv_stop;
public void stop(UvLoopHandle handle)
{
handle.Validate();
_uv_stop(handle);
}
public Action<UvHandle> _uv_ref;
public void @ref(UvHandle handle)
{
handle.Validate();
_uv_ref(handle);
}
public Action<UvHandle> _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<IntPtr, uv_close_cb> _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<UvLoopHandle, UvAsyncHandle, uv_async_cb, int> _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<UvAsyncHandle, int> _uv_async_send;
public void async_send(UvAsyncHandle handle)
{
ThrowIfErrored(_uv_async_send(handle));
}
public Func<IntPtr, int> _uv_unsafe_async_send;
public void unsafe_async_send(IntPtr handle)
{
ThrowIfErrored(_uv_unsafe_async_send(handle));
}
public Func<UvLoopHandle, UvTcpHandle, int> _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<UvTcpHandle, IntPtr, int> _uv_tcp_open;
public void tcp_open(UvTcpHandle handle, IntPtr hSocket)
{
handle.Validate();
ThrowIfErrored(_uv_tcp_open(handle, hSocket));
}
public Func<UvTcpHandle, int, int> _uv_tcp_nodelay;
public void tcp_nodelay(UvTcpHandle handle, bool enable)
{
handle.Validate();
ThrowIfErrored(_uv_tcp_nodelay(handle, enable ? 1 : 0));
}
public Func<UvLoopHandle, UvPipeHandle, int, int> _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<UvPipeHandle, string, int> _uv_pipe_bind;
public void pipe_bind(UvPipeHandle handle, string name)
{
handle.Validate();
ThrowIfErrored(_uv_pipe_bind(handle, name));
}
public Func<UvPipeHandle, IntPtr, int> _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<UvStreamHandle, int, uv_connection_cb, int> _uv_listen;
public void listen(UvStreamHandle handle, int backlog, uv_connection_cb cb)
{
handle.Validate();
ThrowIfErrored(_uv_listen(handle, backlog, cb));
}
public Func<UvStreamHandle, UvStreamHandle, int> _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<UvConnectRequest, UvPipeHandle, string, uv_connect_cb> _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<UvPipeHandle, int> _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<UvStreamHandle, uv_alloc_cb, uv_read_cb, int> _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<UvStreamHandle, int> _uv_read_stop;
public void read_stop(UvStreamHandle handle)
{
handle.Validate();
ThrowIfErrored(_uv_read_stop(handle));
}
public Func<UvStreamHandle, uv_buf_t[], int, int> _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<int, IntPtr> _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<int, IntPtr> _uv_strerror;
public string strerror(int err)
{
IntPtr ptr = _uv_strerror(err);
return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr);
}
public Func<int> _uv_loop_size;
public int loop_size() => _uv_loop_size();
public Func<HandleType, int> _uv_handle_size;
public int handle_size(HandleType handleType) => _uv_handle_size(handleType);
public Func<RequestType, int> _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<UvLoopHandle, uv_walk_cb, IntPtr, int> _uv_walk;
public void walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg)
{
loop.Validate();
_uv_walk(loop, walk_cb, arg);
}
public Func<UvLoopHandle, UvTimerHandle, int> _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<UvTimerHandle, uv_timer_cb, long, long, int> _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<UvTimerHandle, int> _uv_timer_stop;
public void timer_stop(UvTimerHandle handle)
{
handle.Validate();
ThrowIfErrored(_uv_timer_stop(handle));
}
public Func<UvLoopHandle, long> _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();
}
}
}

View file

@ -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;
}
}

View file

@ -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<Action<IntPtr>, IntPtr> _queueCloseHandle;
public UvAsyncHandle(ILibuvTrace logger) : base(logger)
{
}
public void Init(UvLoopHandle loop, Action callback, Action<Action<IntPtr>, 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<UvAsyncHandle>(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;
}
}
}

View file

@ -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>
/// Summary description for UvWriteRequest
/// </summary>
public class UvConnectRequest : UvRequest
{
private static readonly LibuvFunctions.uv_connect_cb _uv_connect_cb = UvConnectCb;
private Action<UvConnectRequest, int, UvException, object> _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<UvConnectRequest, int, UvException, object> 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<UvConnectRequest>(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;
}
}
}
}

View file

@ -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; }
}
}

View file

@ -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<Action<IntPtr>, IntPtr> _queueCloseHandle;
protected UvHandle(ILibuvTrace logger) : base (logger)
{
}
protected void CreateHandle(
LibuvFunctions uv,
int threadId,
int size,
Action<Action<IntPtr>, 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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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>
/// Summary description for UvMemory
/// </summary>
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<THandle>(IntPtr handle)
{
GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle);
return (THandle)gcHandle.Target;
}
}
}

View file

@ -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<Action<IntPtr>, 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);
}
}

View file

@ -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;
}
}
}

View file

@ -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<UvStreamHandle, int, UvException, object> _listenCallback;
private object _listenState;
private GCHandle _listenVitality;
private Func<UvStreamHandle, int, object, LibuvFunctions.uv_buf_t> _allocCallback;
private Action<UvStreamHandle, int, object> _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<UvStreamHandle, int, UvException, object> 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<UvStreamHandle, int, object, LibuvFunctions.uv_buf_t> allocCallback,
Action<UvStreamHandle, int, object> 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<UvStreamHandle>(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<UvStreamHandle>(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<UvStreamHandle>(handle);
try
{
stream._readCallback(stream, status, stream._readState);
}
catch (Exception ex)
{
stream._log.LogError(0, ex, "UbReadCb");
throw;
}
}
}
}

View file

@ -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<Action<IntPtr>, 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<SockAddr>();
_uv.tcp_getpeername(this, out var socketAddress, ref namelen);
return socketAddress.GetIPEndPoint();
}
public IPEndPoint GetSockIPEndPoint()
{
int namelen = Marshal.SizeOf<SockAddr>();
_uv.tcp_getsockname(this, out var socketAddress, ref namelen);
return socketAddress.GetIPEndPoint();
}
public void NoDelay(bool enable)
{
_uv.tcp_nodelay(this, enable);
}
}
}

View file

@ -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<UvTimerHandle> _callback;
public UvTimerHandle(ILibuvTrace logger) : base(logger)
{
}
public void Init(UvLoopHandle loop, Action<Action<IntPtr>, IntPtr> queueCloseHandle)
{
CreateHandle(
loop.Libuv,
loop.ThreadId,
loop.Libuv.handle_size(LibuvFunctions.HandleType.TIMER),
queueCloseHandle);
_uv.timer_init(loop, this);
}
public void Start(Action<UvTimerHandle> 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<UvTimerHandle>(handle);
try
{
timer._callback(timer);
}
catch (Exception ex)
{
timer._log.LogError(0, ex, nameof(UvTimerCb));
throw;
}
}
}
}

View file

@ -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>
/// Summary description for UvWriteRequest
/// </summary>
public class UvWriteReq : UvRequest
{
private static readonly LibuvFunctions.uv_write_cb _uv_write_cb = (ptr, status) => UvWriteCb(ptr, status);
private IntPtr _bufs;
private Action<UvWriteReq, int, UvException, object> _callback;
private object _state;
private const int BUFFER_COUNT = 4;
private LibuvAwaitable<UvWriteReq> _awaitable = new LibuvAwaitable<UvWriteReq>();
private List<GCHandle> _pins = new List<GCHandle>(BUFFER_COUNT + 1);
private List<MemoryHandle> _handles = new List<MemoryHandle>(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<LibuvFunctions.uv_buf_t>() * BUFFER_COUNT;
CreateMemory(
loop.Libuv,
loop.ThreadId,
requestSize + bufferSize);
_bufs = handle + requestSize;
}
public LibuvAwaitable<UvWriteReq> WriteAsync(UvStreamHandle handle, in ReadOnlySequence<byte> buffer)
{
Write(handle, buffer, LibuvAwaitable<UvWriteReq>.Callback, _awaitable);
return _awaitable;
}
public LibuvAwaitable<UvWriteReq> WriteAsync(UvStreamHandle handle, ArraySegment<ArraySegment<byte>> bufs)
{
Write(handle, bufs, LibuvAwaitable<UvWriteReq>.Callback, _awaitable);
return _awaitable;
}
private unsafe void Write(
UvStreamHandle handle,
in ReadOnlySequence<byte> buffer,
Action<UvWriteReq, int, UvException, object> 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<ArraySegment<byte>> bufs,
Action<UvWriteReq, int, UvException, object> callback,
object state)
{
WriteArraySegmentInternal(handle, bufs, null, callback, state);
}
public void Write2(
UvStreamHandle handle,
ArraySegment<ArraySegment<byte>> bufs,
UvStreamHandle sendHandle,
Action<UvWriteReq, int, UvException, object> callback,
object state)
{
WriteArraySegmentInternal(handle, bufs, sendHandle, callback, state);
}
private unsafe void WriteArraySegmentInternal(
UvStreamHandle handle,
ArraySegment<ArraySegment<byte>> bufs,
UvStreamHandle sendHandle,
Action<UvWriteReq, int, UvException, object> 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<UvWriteReq>(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;
}
}
}
}

View file

@ -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<TRequest> : ICriticalNotifyCompletion where TRequest : UvRequest
{
private static readonly Action _callbackCompleted = () => { };
private Action _callback;
private UvException _exception;
private int _status;
public static readonly Action<TRequest, int, UvException, object> Callback = (req, status, error, state) =>
{
var awaitable = (LibuvAwaitable<TRequest>)state;
awaitable._exception = error;
awaitable._status = status;
var continuation = Interlocked.Exchange(ref awaitable._callback, _callbackCompleted);
continuation?.Invoke();
};
public LibuvAwaitable<TRequest> 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<TRequest>)}.{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;
}
}
}

View file

@ -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<UvStreamHandle, int, object> _readCallback = ReadCallback;
private static readonly Func<UvStreamHandle, int, object, LibuvFunctions.uv_buf_t> _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<object> _waitForConnectionClosedTcs = new TaskCompletionSource<object>(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<byte> 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<FlushResult> 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)}.");
}
}
}
}

View file

@ -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<ListenerContext> _listeners = new List<ListenerContext>();
private IAsyncEnumerator<LibuvConnection> _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<LibuvThread> Threads { get; } = new List<LibuvThread>();
public IHostApplicationLifetime AppLifetime => TransportContext.AppLifetime;
public ILibuvTrace Log => TransportContext.Log;
public LibuvTransportOptions TransportOptions => TransportContext.Options;
public EndPoint EndPoint { get; set; }
public async ValueTask<ConnectionContext> 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<LibuvConnection> 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<bool> WaitAsync(Task task, TimeSpan timeout) => await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}
}
}

View file

@ -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<object> _threadTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly UvLoopHandle _loop;
private readonly UvAsyncHandle _post;
private Queue<Work> _workAdding = new Queue<Work>(1024);
private Queue<Work> _workRunning = new Queue<Work>(1024);
private Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>(256);
private Queue<CloseHandle> _closeHandleRunning = new Queue<CloseHandle>(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<byte> 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<byte> MemoryPool { get; }
public WriteReqPool WriteReqPool { get; }
#if DEBUG
public List<WeakReference> Requests { get; } = new List<WeakReference>();
#endif
public Exception FatalError => _closeError;
public Action<Action<IntPtr>, IntPtr> QueueCloseHandle { get; }
private Action<Action<IntPtr>, IntPtr> QueueCloseAsyncHandle { get; }
public Task StartAsync()
{
var tcs = new TaskCompletionSource<int>(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<UvHandle>(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<T>(Action<T> callback, T state)
{
// Handle is closed to don't bother scheduling anything
if (_post.IsClosed) return;
var work = new Work
{
CallbackAdapter = CallbackAdapter<T>.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<LibuvThread> callback)
{
Post(callback, this);
}
public Task PostAsync<T>(Action<T> callback, T state)
{
// Handle is closed to don't bother scheduling anything
if (_post.IsClosed) return Task.CompletedTask;
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
var work = new Work
{
CallbackAdapter = CallbackAdapter<T>.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<IntPtr> 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<IntPtr> callback, IntPtr handle)
{
EnqueueCloseHandle(callback, handle);
_post.Send();
}
private void EnqueueCloseHandle(Action<IntPtr> 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<int>)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<UvHandle>(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<Work> 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<CloseHandle> 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<bool> WaitAsync(Task task, TimeSpan timeout) =>
await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
public override void Schedule(Action<object> action, object state)
{
Post(action, state);
}
private struct Work
{
public Action<object, object> CallbackAdapter;
public object Callback;
public object State;
public TaskCompletionSource<object> Completion;
}
private struct CloseHandle
{
public Action<IntPtr> Callback;
public IntPtr Handle;
}
private class CallbackAdapter<T>
{
public static readonly Action<object, object> PostCallbackAdapter = (callback, state) => ((Action<T>)callback).Invoke((T)state);
public static readonly Action<object, object> PostAsyncCallbackAdapter = (callback, state) => ((Action<T>)callback).Invoke((T)state);
}
}
}

View file

@ -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<ILogger, string, Exception> _connectionPause =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(4, nameof(ConnectionPause)), @"Connection id ""{ConnectionId}"" paused.");
private static readonly Action<ILogger, string, Exception> _connectionResume =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(5, nameof(ConnectionResume)), @"Connection id ""{ConnectionId}"" resumed.");
private static readonly Action<ILogger, string, Exception> _connectionReadFin =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(6, nameof(ConnectionReadFin)), @"Connection id ""{ConnectionId}"" received FIN.");
private static readonly Action<ILogger, string, string, Exception> _connectionWriteFin =
LoggerMessage.Define<string, string>(LogLevel.Debug, new EventId(7, nameof(ConnectionWriteFin)), @"Connection id ""{ConnectionId}"" sending FIN because: ""{Reason}""");
// ConnectionWrite: Reserved: 11
// ConnectionWriteCallback: Reserved: 12
private static readonly Action<ILogger, string, Exception> _connectionError =
LoggerMessage.Define<string>(LogLevel.Debug, new EventId(14, nameof(ConnectionError)), @"Connection id ""{ConnectionId}"" communication error.");
private static readonly Action<ILogger, string, Exception> _connectionReset =
LoggerMessage.Define<string>(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>(TState state) => _logger.BeginScope(state);
public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
=> _logger.Log(logLevel, eventId, state, exception, formatter);
}
}

View file

@ -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; }
}
}

View file

@ -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
{
/// <summary>
/// Provides programmatic configuration of Libuv transport features.
/// </summary>
public class LibuvTransportOptions
{
/// <summary>
/// The number of libuv I/O threads used to process requests.
/// </summary>
/// <remarks>
/// Defaults to half of <see cref="Environment.ProcessorCount" /> rounded down and clamped between 1 and 16.
/// </remarks>
public int ThreadCount { get; set; } = ProcessorThreadCount;
/// <summary>
/// The maximum length of the pending connection queue.
/// </summary>
/// <remarks>
/// Defaults to 128.
/// </remarks>
public int Backlog { get; set; } = 128;
public long? MaxReadBufferSize { get; set; } = 1024 * 1024;
public long? MaxWriteBufferSize { get; set; } = 64 * 1024;
internal Func<MemoryPool<byte>> 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;
}
}
}
}

View file

@ -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
{
/// <summary>
/// Base class for listeners in Kestrel. Listens for incoming connections
/// </summary>
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);
}
/// <summary>
/// Creates the socket used to listen for incoming connections
/// </summary>
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);
}
/// <summary>
/// Handles an incoming connection
/// </summary>
/// <param name="listenSocket">Socket being used to listen on</param>
/// <param name="status">Connection status</param>
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;
}
}
}

View file

@ -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<LibuvConnection> _acceptQueue = Channel.CreateUnbounded<LibuvConnection>(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<LibuvConnection> AcceptAsync(CancellationToken cancellationToken = default)
{
while (await _acceptQueue.Reader.WaitToReadAsync())
while (_acceptQueue.Reader.TryRead(out var connection))
return connection;
return null;
}
/// <summary>
/// Aborts all unaccepted connections in the queue
/// </summary>
/// <returns></returns>
public async Task AbortQueuedConnectionAsync()
{
while (await _acceptQueue.Reader.WaitToReadAsync())
while (_acceptQueue.Reader.TryRead(out var connection))
// REVIEW: Pass an abort reason?
connection.Abort();
}
/// <summary>
/// Creates a socket which can be used to accept an incoming connection.
/// </summary>
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();
}
}
}
}

View file

@ -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
{
/// <summary>
/// A primary listener waits for incoming connections on a specified socket. Incoming
/// connections may be passed to a secondary listener to handle.
/// </summary>
public class ListenerPrimary : Listener
{
// The list of pipes that can be dispatched to (where we've confirmed the _pipeMessage)
private readonly List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>();
// The list of pipes we've created but may not be part of _dispatchPipes
private readonly List<UvPipeHandle> _createdPipes = new List<UvPipeHandle>();
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<ArraySegment<byte>> _dummyMessage = new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) });
public ListenerPrimary(LibuvTransportContext transportContext) : base(transportContext)
{
}
/// <summary>
/// For testing purposes.
/// </summary>
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<FILE_COMPLETION_INFORMATION>(), 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");
}
}
}
}
}

View file

@ -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
{
/// <summary>
/// A secondary listener is delegated requests from a primary listener via a named pipe or
/// UNIX domain socket.
/// </summary>
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<int>(this, TaskCreationOptions.RunContinuationsAsynchronously);
Thread.Post(StartCallback, tcs);
return tcs.Task;
}
private static void StartCallback(TaskCompletionSource<int> tcs)
{
var listener = (ListenerSecondary)tcs.Task.AsyncState;
listener.StartedCallback(tcs);
}
private void StartedCallback(TaskCompletionSource<int> 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<int>)state),
tcs);
}
catch (Exception ex)
{
DispatchPipe.Dispose();
connect.Dispose();
tcs.SetException(ex);
}
}
private static void ConnectCallback(UvConnectRequest connect, int status, UvException error, TaskCompletionSource<int> tcs)
{
var listener = (ListenerSecondary)tcs.Task.AsyncState;
_ = listener.ConnectedCallback(connect, status, error, tcs);
}
private async Task ConnectedCallback(UvConnectRequest connect, int status, UvException error, TaskCompletionSource<int> 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<ArraySegment<byte>>(new[] { new ArraySegment<byte>(_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();
}
}
}
}

View file

@ -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<UvWriteReq> _pool = new Queue<UvWriteReq>(_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();
}
}
}
}

View file

@ -143,9 +143,13 @@ namespace Server
public static int ProcessorCount{ get; private set; } 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 public static string BaseDirectory
{ {
@ -261,8 +265,9 @@ namespace Server
{ {
try try
{ {
foreach (Listener l in MessagePump.Listeners) Task.WhenAll(
l.Dispose(); MessagePump.Listeners.Select(listener => listener.Dispose())
).Wait();
} }
catch catch
{ {
@ -413,14 +418,7 @@ namespace Server
Console.WriteLine("Core: Optimizing for {0} {2}processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s", Console.WriteLine("Core: Optimizing for {0} {2}processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s",
Is64Bit ? "64-bit " : ""); Is64Bit ? "64-bit " : "");
int platform = (int)Environment.OSVersion.Platform; if (IsWindows)
if (platform == 4 || platform == 128)
{
// MS 4, MONO 128
Unix = true;
Console.WriteLine("Core: Unix environment detected");
}
else
{ {
m_ConsoleEventHandler = OnConsoleEvent; m_ConsoleEventHandler = OnConsoleEvent;
UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true); UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true);

View file

@ -1445,7 +1445,7 @@ namespace Server
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)]
public NetState NetState 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 set
{ {
if (m_NetState != value) if (m_NetState != value)

View file

@ -24,53 +24,54 @@ using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Libuv;
using Libuv.Internal;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace Server.Network namespace Server.Network
{ {
public class Listener public class Listener
{ {
private Socket m_Socket; private static readonly LibuvFunctions functions = new LibuvFunctions();
private IPEndPoint m_EndPoint;
private readonly IPEndPoint m_EndPoint;
private LibuvConnectionListener m_Listener;
public Listener(IPEndPoint ipep) 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; m_EndPoint = ipep;
LibuvTransportContext transport = new LibuvTransportContext
{
Options = new LibuvTransportOptions(),
AppLifetime = new ApplicationLifetime(
LoggerFactory.Create(builder => { builder.AddConsole(); }).CreateLogger<ApplicationLifetime>()
),
Log = new LibuvTrace(LoggerFactory.Create(builder => { builder.AddConsole(); }).CreateLogger("network"))
};
m_Listener = new LibuvConnectionListener(functions, transport, ipep);
} }
public virtual async Task Start(MessagePump pump) public virtual async Task Start(MessagePump pump)
{ {
try try
{ {
m_Socket.Bind(m_EndPoint); await m_Listener.BindAsync();
m_Socket.Listen(8); }
catch (AddressInUseException)
{
Console.WriteLine("Listener Failed: {0}:{1} (In Use)", m_EndPoint.Address, m_EndPoint.Port);
m_Listener = null;
return;
} }
catch (Exception e) catch (Exception e)
{ {
if (e is SocketException se) Console.WriteLine("Listener Exception:");
{ Console.WriteLine(e);
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);
}
}
m_Socket = null; m_Listener = null;
return; return;
} }
@ -78,10 +79,10 @@ namespace Server.Network
while (true) while (true)
{ {
Socket s; ConnectionContext context;
try try
{ {
s = await m_Socket.AcceptAsync().ConfigureAwait(false); context = await m_Listener.AcceptAsync();
} }
catch (SocketException ex) catch (SocketException ex)
{ {
@ -89,16 +90,16 @@ namespace Server.Network
continue; continue;
} }
if (VerifySocket(s)) if (VerifySocket(context))
_ = new NetState(s, pump); _ = new NetState(context, pump);
else else
Release(s); Release(context);
} }
} }
private void DisplayListener() private void DisplayListener()
{ {
if (!(m_Socket.LocalEndPoint is IPEndPoint ipep)) if (!(m_Listener.EndPoint is IPEndPoint ipep))
return; return;
if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any))
@ -113,16 +114,14 @@ namespace Server.Network
} }
} }
else else
{
Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port); Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port);
}
} }
private bool VerifySocket(Socket socket) private static bool VerifySocket(ConnectionContext context)
{ {
try try
{ {
SocketConnectEventArgs args = new SocketConnectEventArgs(socket); SocketConnectEventArgs args = new SocketConnectEventArgs(context);
EventSink.InvokeSocketConnect(args); EventSink.InvokeSocketConnect(args);
@ -131,44 +130,47 @@ namespace Server.Network
catch (Exception ex) catch (Exception ex)
{ {
NetState.TraceException(ex); NetState.TraceException(ex);
return false; return false;
} }
} }
private void Release(Socket socket) private static void Release(ConnectionContext context)
{ {
try try
{ {
socket.Shutdown(SocketShutdown.Both); context.Abort(new ConnectionAbortedException("Failed socket verification."));
} }
catch (SocketException ex) catch (Exception ex)
{ {
NetState.TraceException(ex); NetState.TraceException(ex);
} }
try try
{ {
socket.Close(); // TODO: Is this needed?
context.DisposeAsync();
} }
catch (SocketException ex) catch (Exception ex)
{
NetState.TraceException(ex);
}
try
{
socket.Dispose();
}
catch (SocketException ex)
{ {
NetState.TraceException(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); GC.SuppressFinalize(this);
} }
} }

View file

@ -23,6 +23,8 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Net; using System.Net;
using System.Threading.Tasks;
using SignalR;
namespace Server.Network namespace Server.Network
{ {
@ -40,9 +42,9 @@ namespace Server.Network
listeners[Listeners.Length] = listener; listeners[Listeners.Length] = listener;
} }
public void QueueWork(NetState ns, in ReadOnlySequence<byte> seq, OnPacketReceive onReceive) public void QueueWork(NetState ns, IMemoryOwner<byte> memOwner, OnPacketReceive onReceive)
{ {
m_WorkQueue.Enqueue(new Work(ns, seq, onReceive)); m_WorkQueue.Enqueue(new Work(ns, memOwner, onReceive));
Core.Set(); Core.Set();
} }
@ -54,21 +56,22 @@ namespace Server.Network
if (!m_WorkQueue.TryDequeue(out Work work)) if (!m_WorkQueue.TryDequeue(out Work work))
break; break;
work.OnReceive(work.State, new PacketReader(work.Sequence)); work.OnReceive(work.State, new PacketReader(new ReadOnlySequence<byte>(work.MemoryOwner.Memory)));
work.MemoryOwner.Dispose();
} }
} }
// TODO: Optimize this with a pool
private class Work private class Work
{ {
public NetState State; public readonly NetState State;
public ReadOnlySequence<byte> Sequence; // TODO: Force dispose?
public OnPacketReceive OnReceive; public readonly IMemoryOwner<byte> MemoryOwner;
public readonly OnPacketReceive OnReceive;
public Work(NetState ns, in ReadOnlySequence<byte> seq, OnPacketReceive onReceive) public Work(NetState ns, IMemoryOwner<byte> memOwner, OnPacketReceive onReceive)
{ {
State = ns; State = ns;
Sequence = seq; MemoryOwner = memOwner;
OnReceive = onReceive; OnReceive = onReceive;
} }
} }

View file

@ -28,6 +28,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Server.Accounting; using Server.Accounting;
using Server.Diagnostics; using Server.Diagnostics;
using Server.Gumps; using Server.Gumps;
@ -93,7 +94,6 @@ namespace Server.Network
{ {
private string m_ToString; private string m_ToString;
private ClientVersion m_Version; private ClientVersion m_Version;
private SendQueue<Packet> m_SendQueue = new SendQueue<Packet>();
public DateTime ConnectedOn { get; } public DateTime ConnectedOn { get; }
@ -266,11 +266,9 @@ namespace Server.Network
return newTrade.From.Container; return newTrade.From.Container;
} }
public bool Running { get; private set; }
public bool Seeded { get; set; } public bool Seeded { get; set; }
public Socket Socket { get; private set; } public ConnectionContext Connection { get; private set; }
public bool CompressionEnabled { get; set; } public bool CompressionEnabled { get; set; }
@ -282,12 +280,16 @@ namespace Server.Network
public List<IMenu> Menus { get; private set; } public List<IMenu> 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 GumpCap { get; set; } = 512;
public static int HuePickerCap { get; set; } = 512; public static int HuePickerCap { get; set; } = 512;
public static int MenuCap { get; set; } = 512; public static int MenuCap { get; set; } = 512;
public void WriteConsole(string text) public void WriteConsole(string text)
{ {
Console.WriteLine("Client: {0}: {1}", this, text); Console.WriteLine("Client: {0}: {1}", this, text);
@ -403,23 +405,24 @@ namespace Server.Network
public static List<NetState> Instances { get; } = new List<NetState>(); public static List<NetState> Instances { get; } = new List<NetState>();
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; Seeded = false;
Running = false;
Gumps = new List<Gump>(); Gumps = new List<Gump>();
HuePickers = new List<HuePicker>(); HuePickers = new List<HuePicker>();
Menus = new List<IMenu>(); Menus = new List<IMenu>();
Trades = new List<SecureTrade>(); Trades = new List<SecureTrade>();
m_NextCheckActivity = Core.TickCount + 30000; SetConnectionAlive();
Instances.Add(this); Instances.Add(this);
try try
{ {
Address = Utility.Intern(((IPEndPoint)Socket.RemoteEndPoint).Address); Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address);
m_ToString = Address.ToString(); m_ToString = Address.ToString();
} }
catch (Exception ex) catch (Exception ex)
@ -430,10 +433,11 @@ namespace Server.Network
} }
ConnectedOn = DateTime.UtcNow; ConnectedOn = DateTime.UtcNow;
_ = Start(pump); Console.WriteLine("Client: {0}: Connected. [{1} Online]", this, Instances.Count);
_ = ProcessRecvs(pump);
CreatedCallback?.Invoke(this); CreatedCallback?.Invoke(this);
Console.WriteLine("Client: {0}: Connected. [{1} Online]", this, NetState.Instances.Count);
} }
public static void Pause() public static void Pause()
@ -448,13 +452,38 @@ namespace Server.Network
public virtual void Send(Packet p) public virtual void Send(Packet p)
{ {
if (Socket == null || BlockAllPackets) if (Connection == null || BlockAllPackets)
{ {
p.OnSend(); p.OnSend();
return; return;
} }
m_SendQueue.Enqueue(p); try
{
ReadOnlySpan<byte> 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) public bool CheckEncrypted(int packetID)
@ -470,121 +499,31 @@ namespace Server.Network
return false; return false;
} }
private Pipe m_RecvdPipe; private async Task ProcessRecvs(MessagePump pump)
private async Task Start(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) while (true)
{ {
if (m_AsyncState.Paused) ReadResult result = await RecvPipe.ReadAsync();
{
await Timer.Pause(50).ConfigureAwait(false);
continue;
}
try
{
Memory<byte> 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<byte> 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();
ReadOnlySequence<byte> seq = result.Buffer; ReadOnlySequence<byte> seq = result.Buffer;
if (seq.Length == 0)
if (seq.IsEmpty)
break; break;
long pos = PacketHandlers.ProcessPacket(pump, this, seq); SetConnectionAlive();
int pos = PacketHandlers.ProcessPacket(pump, this, seq);
if (pos <= 0) if (pos <= 0)
break; break;
pr.AdvanceTo(seq.GetPosition(pos, seq.Start)); RecvPipe.AdvanceTo(seq.Slice(0, pos).End);
if (result.IsCompleted || result.IsCanceled) if (result.IsCompleted || result.IsCanceled)
break; break;
} }
pr.Complete(); RecvPipe.Complete();
Dispose();
} }
public PacketHandler GetHandler(int packetID) => public PacketHandler GetHandler(int packetID) =>
@ -595,7 +534,7 @@ namespace Server.Network
public void CheckAlive(long curTicks) public void CheckAlive(long curTicks)
{ {
if (Socket == null || m_NextCheckActivity - curTicks >= 0) if (Connection == null || m_NextCheckActivity - curTicks >= 0)
return; return;
Console.WriteLine("Client: {0}: Disconnecting due to inactivity...", this); Console.WriteLine("Client: {0}: Disconnecting due to inactivity...", this);
@ -637,21 +576,18 @@ namespace Server.Network
if (disposing == 1) if (disposing == 1)
return; return;
Task.Run(async () => try
{ {
m_RecvdPipe.Reader.CancelPendingRead(); Connection.Abort();
m_RecvdPipe.Reader.Complete(); Task.Run(Connection.DisposeAsync).Wait();
await m_RecvdPipe.Writer.FlushAsync().ConfigureAwait(false); }
m_RecvdPipe.Writer.Complete(); catch (Exception ex)
{
TraceException(ex);
}
try { Socket.Shutdown(SocketShutdown.Both); } catch (Exception ex) { TraceException(ex); } Connection = null;
try { Socket.Close(); } catch (Exception ex) { TraceException(ex); } m_Disposed.Enqueue(this);
Socket = null;
m_RecvdPipe = null;
m_SendQueue = null;
m_Disposed.Enqueue(this);
});
} }
public static void Initialize() public static void Initialize()

View file

@ -28,7 +28,7 @@ namespace Server.Network
public class PacketHandler 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; PacketID = packetID;
Length = length; Length = length;
@ -38,7 +38,7 @@ namespace Server.Network
public int PacketID{ get; } public int PacketID{ get; }
public long Length{ get; } public int Length{ get; }
public OnPacketReceive OnReceive{ get; } public OnPacketReceive OnReceive{ get; }

View file

@ -109,43 +109,32 @@ namespace Server.Network
Register(0x01, 5, false, Disconnect); Register(0x01, 5, false, Disconnect);
Register(0x02, 7, true, MovementReq); Register(0x02, 7, true, MovementReq);
Register(0x03, 0, true, AsciiSpeech); Register(0x03, 0, true, AsciiSpeech);
Register(0x04, 2, true, GodModeRequest);
Register(0x05, 5, true, AttackReq); Register(0x05, 5, true, AttackReq);
Register(0x06, 5, true, UseReq); Register(0x06, 5, true, UseReq);
Register(0x07, 7, true, LiftReq); Register(0x07, 7, true, LiftReq);
Register(0x08, 14, true, DropReq); Register(0x08, 14, true, DropReq);
Register(0x09, 5, true, LookReq); Register(0x09, 5, true, LookReq);
Register(0x0A, 11, true, Edit);
Register(0x12, 0, true, TextCommand); Register(0x12, 0, true, TextCommand);
Register(0x13, 10, true, EquipReq); Register(0x13, 10, true, EquipReq);
Register(0x14, 6, true, ChangeZ);
Register(0x22, 3, true, Resynchronize); Register(0x22, 3, true, Resynchronize);
Register(0x2C, 2, true, DeathStatusResponse); Register(0x2C, 2, true, DeathStatusResponse);
Register(0x34, 10, true, MobileQuery); Register(0x34, 10, true, MobileQuery);
Register(0x3A, 0, true, ChangeSkillLock); Register(0x3A, 0, true, ChangeSkillLock);
Register(0x3B, 0, true, VendorBuyReply); 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(0x5D, 73, false, PlayCharacter);
Register(0x61, 9, true, DeleteStatic);
Register(0x6C, 19, true, TargetResponse); Register(0x6C, 19, true, TargetResponse);
Register(0x6F, 0, true, SecureTrade); Register(0x6F, 0, true, SecureTrade);
Register(0x72, 5, true, SetWarMode); Register(0x72, 5, true, SetWarMode);
Register(0x73, 2, false, PingReq); Register(0x73, 2, false, PingReq);
Register(0x75, 35, true, RenameRequest); Register(0x75, 35, true, RenameRequest);
Register(0x79, 9, true, ResourceQuery);
Register(0x7E, 2, true, GodviewQuery);
Register(0x7D, 13, true, MenuResponse); Register(0x7D, 13, true, MenuResponse);
Register(0x80, 62, false, AccountLogin); Register(0x80, 62, false, AccountLogin);
Register(0x83, 39, false, DeleteCharacter); Register(0x83, 39, false, DeleteCharacter);
Register(0x91, 65, false, GameLogin); Register(0x91, 65, false, GameLogin);
Register(0x95, 9, true, HuePickerResponse); Register(0x95, 9, true, HuePickerResponse);
Register(0x96, 0, true, GameCentralMoniter);
Register(0x98, 0, true, MobileNameRequest); Register(0x98, 0, true, MobileNameRequest);
Register(0x9A, 0, true, AsciiPromptResponse); Register(0x9A, 0, true, AsciiPromptResponse);
Register(0x9B, 258, true, HelpRequest); Register(0x9B, 258, true, HelpRequest);
Register(0x9D, 51, true, GMSingle);
Register(0x9F, 0, true, VendorSellReply); Register(0x9F, 0, true, VendorSellReply);
Register(0xA0, 3, false, PlayServer); Register(0xA0, 3, false, PlayServer);
Register(0xA4, 149, false, SystemInfo); Register(0xA4, 149, false, SystemInfo);
@ -161,8 +150,6 @@ namespace Server.Network
Register(0xBF, 0, true, ExtendedCommand); Register(0xBF, 0, true, ExtendedCommand);
Register(0xC2, 0, true, UnicodePromptResponse); Register(0xC2, 0, true, UnicodePromptResponse);
Register(0xC8, 2, true, SetUpdateRange); Register(0xC8, 2, true, SetUpdateRange);
Register(0xC9, 6, true, TripTime);
Register(0xCA, 6, true, UTripTime);
Register(0xCF, 0, false, AccountLogin); Register(0xCF, 0, false, AccountLogin);
Register(0xD0, 0, true, ConfigurationFile); Register(0xD0, 0, true, ConfigurationFile);
Register(0xD1, 2, true, LogoutReq); Register(0xD1, 2, true, LogoutReq);
@ -172,6 +159,7 @@ namespace Server.Network
Register(0xEF, 21, false, LoginServerSeed); Register(0xEF, 21, false, LoginServerSeed);
Register(0xF4, 0, false, CrashReport); Register(0xF4, 0, false, CrashReport);
Register(0xF8, 106, false, CreateCharacter70160); Register(0xF8, 106, false, CreateCharacter70160);
Register(0xFB, 2, false, ShowPublicHouseContent);
Register6017(0x08, 15, true, DropReq6017); Register6017(0x08, 15, true, DropReq6017);
@ -214,9 +202,7 @@ namespace Server.Network
public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive) public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive)
{ {
Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive); Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive);
if (m_6017Handlers[packetID] == null)
m_6017Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
} }
public static PacketHandler GetHandler(int packetID) => Handlers[packetID]; public static PacketHandler GetHandler(int packetID) => Handlers[packetID];
@ -291,7 +277,9 @@ namespace Server.Network
ph.ThrottleCallback = t; ph.ThrottleCallback = t;
} }
public static long ProcessPacket(MessagePump pump, NetState ns, in ReadOnlySequence<byte> seq) private static MemoryPool<byte> _memoryPool = SlabMemoryPoolFactory.Create();
public static int ProcessPacket(MessagePump pump, NetState ns, in ReadOnlySequence<byte> seq)
{ {
PacketReader r = new PacketReader(seq); PacketReader r = new PacketReader(seq);
@ -342,7 +330,7 @@ namespace Server.Network
return -1; return -1;
} }
long packetLength = handler.Length; int packetLength = handler.Length;
if (handler.Length <= 0 && r.Length >= 3) if (handler.Length <= 0 && r.Length >= 3)
{ {
packetLength = r.ReadUInt16(); packetLength = r.ReadUInt16();
@ -373,16 +361,12 @@ namespace Server.Network
if (throttled > TimeSpan.Zero) if (throttled > TimeSpan.Zero)
ns.ThrottledUntil = DateTime.UtcNow + throttled; ns.ThrottledUntil = DateTime.UtcNow + throttled;
PacketReceiveProfile prof = null; ReadOnlySequence<byte> packet = seq.Slice(r.Position);
IMemoryOwner<byte> memOwner = _memoryPool.Rent((int)packet.Length);
if (Core.Profiling) packet.CopyTo(memOwner.Memory.Span);
prof = PacketReceiveProfile.Acquire(packetId);
prof?.Start(); pump.QueueWork(ns, memOwner, handler.OnReceive);
pump.QueueWork(ns, seq.Slice(r.Position), handler.OnReceive);
prof?.Finish(packetLength);
return packetLength; return packetLength;
} }
@ -413,9 +397,9 @@ namespace Server.Network
public static void EncodedCommand(NetState state, PacketReader pvSrc) public static void EncodedCommand(NetState state, PacketReader pvSrc)
{ {
IEntity e = World.FindEntity(pvSrc.ReadUInt32()); IEntity e = World.FindEntity(pvSrc.ReadUInt32());
int packetID = pvSrc.ReadUInt16(); int packetId = pvSrc.ReadUInt16();
EncodedPacketHandler ph = GetEncodedHandler(packetID); EncodedPacketHandler ph = GetEncodedHandler(packetId);
if (ph != null) if (ph != null)
{ {
@ -423,7 +407,7 @@ namespace Server.Network
{ {
Console.WriteLine( Console.WriteLine(
"Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile", state, "Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile", state,
packetID); packetId);
state.Dispose(); state.Dispose();
} }
else if (ph.Ingame && state.Mobile.Deleted) else if (ph.Ingame && state.Mobile.Deleted)
@ -609,39 +593,6 @@ namespace Server.Network
EventSink.InvokeDeleteRequest(new DeleteRequestEventArgs(state, index)); EventSink.InvokeDeleteRequest(new DeleteRequestEventArgs(state, index));
} }
public static void ResourceQuery(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
}
}
public static void GameCentralMoniter(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
int type = pvSrc.ReadByte();
int num1 = pvSrc.ReadInt32();
Console.WriteLine("God Client: {0}: Game central moniter", state);
Console.WriteLine(" - Type: {0}", type);
Console.WriteLine(" - Number: {0}", num1);
pvSrc.Trace(state);
}
}
public static void GodviewQuery(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state)) Console.WriteLine("God Client: {0}: Godview query 0x{1:X}", state, pvSrc.ReadByte());
}
public static void GMSingle(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
pvSrc.Trace(state);
}
public static void DeathStatusResponse(NetState state, PacketReader pvSrc) public static void DeathStatusResponse(NetState state, PacketReader pvSrc)
{ {
// Ignored // Ignored
@ -711,35 +662,7 @@ namespace Server.Network
huePicker.OnResponse(hue); huePicker.OnResponse(hue);
break; break;
}
} }
public static void TripTime(NetState state, PacketReader pvSrc)
{
int unk1 = pvSrc.ReadByte();
int unk2 = pvSrc.ReadInt32();
state.Send(new TripTimeResponse(unk1));
}
public static void UTripTime(NetState state, PacketReader pvSrc)
{
int unk1 = pvSrc.ReadByte();
int unk2 = pvSrc.ReadInt32();
state.Send(new UTripTimeResponse(unk1));
}
public static void ChangeZ(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
int x = pvSrc.ReadInt16();
int y = pvSrc.ReadInt16();
int z = pvSrc.ReadSByte();
Console.WriteLine("God Client: {0}: Change Z ({1}, {2}, {3})", state, x, y, z);
}
} }
public static void SystemInfo(NetState state, PacketReader pvSrc) public static void SystemInfo(NetState state, PacketReader pvSrc)
@ -758,101 +681,10 @@ namespace Server.Network
int v8 = pvSrc.ReadInt32(); int v8 = pvSrc.ReadInt32();
} }
public static void Edit(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
int type = pvSrc.ReadByte(); // 10 = static, 7 = npc, 4 = dynamic
int x = pvSrc.ReadInt16();
int y = pvSrc.ReadInt16();
int id = pvSrc.ReadInt16();
int z = pvSrc.ReadSByte();
int hue = pvSrc.ReadUInt16();
Console.WriteLine("God Client: {0}: Edit {6} ({1}, {2}, {3}) 0x{4:X} (0x{5:X})", state, x, y, z, id, hue,
type);
}
}
public static void DeleteStatic(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
int x = pvSrc.ReadInt16();
int y = pvSrc.ReadInt16();
int z = pvSrc.ReadInt16();
int id = pvSrc.ReadUInt16();
Console.WriteLine("God Client: {0}: Delete Static ({1}, {2}, {3}) 0x{4:X}", state, x, y, z, id);
}
}
public static void NewAnimData(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
Console.WriteLine("God Client: {0}: New tile animation", state);
pvSrc.Trace(state);
}
}
public static void NewTerrain(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
int x = pvSrc.ReadInt16();
int y = pvSrc.ReadInt16();
int id = pvSrc.ReadUInt16();
int width = pvSrc.ReadInt16();
int height = pvSrc.ReadInt16();
Console.WriteLine("God Client: {0}: New Terrain ({1}, {2})+({3}, {4}) 0x{5:X4}", state, x, y, width, height,
id);
}
}
public static void NewRegion(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
{
string name = pvSrc.ReadString(40);
int unk = pvSrc.ReadInt32();
int x = pvSrc.ReadInt16();
int y = pvSrc.ReadInt16();
int width = pvSrc.ReadInt16();
int height = pvSrc.ReadInt16();
int zStart = pvSrc.ReadInt16();
int zEnd = pvSrc.ReadInt16();
string desc = pvSrc.ReadString(40);
int soundFX = pvSrc.ReadInt16();
int music = pvSrc.ReadInt16();
int nightFX = pvSrc.ReadInt16();
int dungeon = pvSrc.ReadByte();
int light = pvSrc.ReadInt16();
Console.WriteLine("God Client: {0}: New Region '{1}' ('{2}')", state, name, desc);
}
}
public static void AccountID(NetState state, PacketReader pvSrc) public static void AccountID(NetState state, PacketReader pvSrc)
{ {
} }
public static bool VerifyGC(NetState state)
{
if (state.Mobile == null || state.Mobile.AccessLevel <= AccessLevel.Counselor)
{
if (state.Running)
Console.WriteLine("Warning: {0}: Player using godclient, disconnecting", state);
state.Dispose();
return false;
}
return true;
}
public static void TextCommand(NetState state, PacketReader pvSrc) public static void TextCommand(NetState state, PacketReader pvSrc)
{ {
int type = pvSrc.ReadByte(); int type = pvSrc.ReadByte();
@ -862,34 +694,6 @@ namespace Server.Network
switch (type) switch (type)
{ {
case 0x00: // Go
{
if (VerifyGC(state))
try
{
string[] split = command.Split(' ');
int x = Utility.ToInt32(split[0]);
int y = Utility.ToInt32(split[1]);
int z;
if (split.Length >= 3)
z = Utility.ToInt32(split[2]);
else if (m.Map != null)
z = m.Map.GetAverageZ(x, y);
else
z = 0;
m.Location = new Point3D(x, y, z);
}
catch
{
// ignored
}
break;
}
case 0xC7: // Animate case 0xC7: // Animate
{ {
EventSink.InvokeAnimateRequest(new AnimateRequestEventArgs(m, command)); EventSink.InvokeAnimateRequest(new AnimateRequestEventArgs(m, command));
@ -970,12 +774,6 @@ namespace Server.Network
} }
} }
public static void GodModeRequest(NetState state, PacketReader pvSrc)
{
if (VerifyGC(state))
state.Send(new GodModeReply(pvSrc.ReadBoolean()));
}
public static void AsciiPromptResponse(NetState state, PacketReader pvSrc) public static void AsciiPromptResponse(NetState state, PacketReader pvSrc)
{ {
uint serial = pvSrc.ReadUInt32(); uint serial = pvSrc.ReadUInt32();
@ -1550,9 +1348,7 @@ namespace Server.Network
uint value = pvSrc.ReadUInt32(); uint value = pvSrc.ReadUInt32();
if ((value & ~0x7FFFFFFF) != 0) if ((value & ~0x7FFFFFFF) != 0)
{
from.OnPaperdollRequest(); from.OnPaperdollRequest();
}
else else
{ {
Serial s = value; Serial s = value;
@ -1576,9 +1372,7 @@ namespace Server.Network
from.NextActionTime = Core.TickCount + Mobile.ActionDelay; from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
} }
else else
{
from.SendActionMessage(); from.SendActionMessage();
}
} }
public static void LookReq(NetState state, PacketReader pvSrc) public static void LookReq(NetState state, PacketReader pvSrc)
@ -1594,9 +1388,7 @@ namespace Server.Network
if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m))
{ {
if (SingleClickProps) if (SingleClickProps)
{
m.OnAosSingleClick(from); m.OnAosSingleClick(from);
}
else else
{ {
if (from.Region.OnSingleClick(from, m)) if (from.Region.OnSingleClick(from, m))
@ -1612,9 +1404,7 @@ namespace Server.Network
Utility.InUpdateRange(from.Location, item.GetWorldLocation())) Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{ {
if (SingleClickProps) if (SingleClickProps)
{
item.OnAosSingleClick(from); item.OnAosSingleClick(from);
}
else if (from.Region.OnSingleClick(from, item)) else if (from.Region.OnSingleClick(from, item))
{ {
if (item.Parent is Item item1) if (item.Parent is Item item1)
@ -2062,13 +1852,6 @@ namespace Server.Network
if (m != null) if (m != null)
switch (type) switch (type)
{ {
case 0x00: // Unknown, sent by godclient
{
if (VerifyGC(state))
Console.WriteLine("God Client: {0}: Query 0x{1:X2} on {2} '{3}'", state, type, m.Serial, m.Name);
break;
}
case 0x04: // Stats case 0x04: // Stats
{ {
m.OnStatsQuery(from); m.OnStatsQuery(from);
@ -2094,44 +1877,10 @@ namespace Server.Network
string name = pvSrc.ReadString(30); string name = pvSrc.ReadString(30);
pvSrc.Seek(2, SeekOrigin.Current); pvSrc.Seek(2, SeekOrigin.Current);
int flags = pvSrc.ReadInt32(); int flags = pvSrc.ReadInt32();
/* if (FeatureProtection.DisabledFeatures != 0 && ThirdPartyAuthCallback != null) pvSrc.Seek(24, SeekOrigin.Current);
{
bool authOK = false;
ulong razorFeatures = ((ulong)pvSrc.ReadUInt32() << 32) | pvSrc.ReadUInt32();
if (razorFeatures == (ulong)FeatureProtection.DisabledFeatures)
{
bool doesNotMatch = false;
for (int i = 0; !doesNotMatch && i < m_ThirdPartyAuthKey.Length; i++)
doesNotMatch = pvSrc.ReadByte() != m_ThirdPartyAuthKey[i];
if (!doesNotMatch)
authOK = true;
}
else
{
pvSrc.Seek(16, SeekOrigin.Current);
}
ThirdPartyAuthCallback(state, authOK);
}*/
/* else
{*/
pvSrc.Seek(24, SeekOrigin.Current);
/* }*/
/* if (ThirdPartyHackedCallback != null)
{
pvSrc.Seek(-2, SeekOrigin.Current);
if (pvSrc.ReadUInt16() == 0xDEAD)
ThirdPartyHackedCallback(state, true);
}*/
if (!state.Running)
return;
int charSlot = pvSrc.ReadInt32(); int charSlot = pvSrc.ReadInt32();
int clientIP = pvSrc.ReadInt32(); int clientIP = pvSrc.ReadInt32();
@ -2181,6 +1930,11 @@ namespace Server.Network
} }
} }
public static void ShowPublicHouseContent(NetState state, PacketReader pvSrc)
{
bool showPublicHouseContent = pvSrc.ReadBoolean();
}
public static void DoLogin(NetState state, Mobile m) public static void DoLogin(NetState state, Mobile m)
{ {
state.Send(new LoginConfirm(m)); state.Send(new LoginConfirm(m));
@ -2614,9 +2368,7 @@ namespace Server.Network
state.Send(new CharacterListOld(state.Account, state.CityInfo)); state.Send(new CharacterListOld(state.Account, state.CityInfo));
} }
else else
{
state.Dispose(); state.Dispose();
}
} }
public static void PlayServer(NetState state, PacketReader pvSrc) public static void PlayServer(NetState state, PacketReader pvSrc)
@ -2626,9 +2378,7 @@ namespace Server.Network
IAccount a = state.Account; IAccount a = state.Account;
if (info == null || a == null || index < 0 || index >= info.Length) if (info == null || a == null || index < 0 || index >= info.Length)
{
state.Dispose(); state.Dispose();
}
else else
{ {
ServerInfo si = info[index]; ServerInfo si = info[index];

View file

@ -175,13 +175,13 @@ namespace Server.Network
return sb.ToString(); return sb.ToString();
} }
public bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE; private static bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE;
public string ReadUTF8StringSafe(int fixedLength) public string ReadUTF8StringSafe(int fixedLength)
{ {
string s; string s;
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0', true)) if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span); s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span);
else else
{ {
@ -203,7 +203,7 @@ namespace Server.Network
{ {
string s; string s;
if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0', true)) if (m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0'))
s = Utility.UTF8.GetString(span); s = Utility.UTF8.GetString(span);
else else
{ {
@ -222,7 +222,7 @@ namespace Server.Network
public string ReadUTF8String() => public string ReadUTF8String() =>
Utility.UTF8.GetString( Utility.UTF8.GetString(
m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0', true) ? span : m_Reader.TryReadTo(out ReadOnlySpan<byte> span, (byte)'\0') ? span :
m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray() m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()
); );

View file

@ -4142,14 +4142,6 @@ namespace Server.Network
} }
} }
public sealed class GodModeReply : Packet
{
public GodModeReply(bool reply) : base(0x2B, 2)
{
m_Stream.Write(reply);
}
}
public sealed class PlayServerAck : Packet public sealed class PlayServerAck : Packet
{ {
internal static int m_AuthID = -1; internal static int m_AuthID = -1;

View file

@ -1,22 +1,22 @@
/*************************************************************************** /*************************************************************************
* SocketExtensions.cs * ModernUO *
* ------------------- * Copyright (C) 2019 - ModernUO Development Team *
* begin : August 2, 2019 * Email: hi@modernuo.com *
* copyright : (C) The ModernUO Team * File: SocketExtensions.cs - Created: 2019/08/02 - Updated: 2019/12/24 *
* email : hi@modernuo.com * *
* * This program is free software: you can redistribute it and/or modify *
* $Id$ * 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 *
* This program is free software; you can redistribute it and/or modify * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* it under the terms of the GNU General Public License as published by * GNU General Public License for more details. *
* the Free Software Foundation; either version 2 of the License, or * *
* (at your option) any later version. * You should have received a copy of the GNU General Public License *
* * along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/ *************************************************************************/
using System; using System;
using System.Buffers; using System.Buffers;
@ -34,7 +34,7 @@ namespace Server.Network
public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory) public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
{ {
if (MemoryMarshal.TryGetArray(memory, out var result)) if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> result))
return result; return result;
throw new InvalidOperationException("Buffer backed by array was expected"); throw new InvalidOperationException("Buffer backed by array was expected");
@ -42,7 +42,7 @@ namespace Server.Network
public static ArraySegment<byte> GetArray(this ReadOnlySequence<byte> memory) public static ArraySegment<byte> GetArray(this ReadOnlySequence<byte> memory)
{ {
if (SequenceMarshal.TryGetArray(memory, out var result)) if (SequenceMarshal.TryGetArray(memory, out ArraySegment<byte> result))
return result; return result;
throw new InvalidOperationException("Buffer backed by array was expected"); throw new InvalidOperationException("Buffer backed by array was expected");

View file

@ -1,22 +1,23 @@
/*************************************************************************** /*************************************************************************
* StaticPacketHandlers.cs * ModernUO *
* ------------------- * Copyright (C) 2019 - ModernUO Development Team *
* begin : March 15, 2019 * Email: hi@modernuo.com *
* copyright : (C) The ModernUO Team * File: StaticPacketHandlers.cs *
* email : hi@modernuo.com * Created: 2019/03/15 - Updated: 2019/12/24 *
* * *
* $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 3 of the License, or *
* (at your option) any later version. *
/*************************************************************************** * *
* * This program is distributed in the hope that it will be useful, *
* This program is free software; you can redistribute it and/or modify * but WITHOUT ANY WARRANTY; without even the implied warranty of *
* it under the terms of the GNU General Public License as published by * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* the Free Software Foundation; either version 2 of the License, or * GNU General Public License for more details. *
* (at your option) any later version. * *
* * You should have received a copy of the GNU General Public License *
***************************************************************************/ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Concurrent; using System.Collections.Concurrent;

View file

@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace System.IO.Pipelines
{
public class DuplexPipe : IDuplexPipe
{
public DuplexPipe(PipeReader reader, PipeWriter writer)
{
Input = reader;
Output = writer;
}
public PipeReader Input { get; }
public PipeWriter Output { get; }
public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions)
{
var input = new Pipe(inputOptions);
var output = new Pipe(outputOptions);
var transportToApplication = new DuplexPipe(output.Reader, input.Writer);
var applicationToTransport = new DuplexPipe(input.Reader, output.Writer);
return new DuplexPipePair(applicationToTransport, transportToApplication);
}
// This class exists to work around issues with value tuple on .NET Framework
public readonly struct DuplexPipePair
{
public IDuplexPipe Transport { get; }
public IDuplexPipe Application { get; }
public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application)
{
Transport = transport;
Application = application;
}
}
}
}

View file

@ -7,7 +7,7 @@
</StartupObject> </StartupObject>
<AssemblyName>ModernUO</AssemblyName> <AssemblyName>ModernUO</AssemblyName>
<Win32Resource /> <Win32Resource />
<Version>0.0.1</Version> <Version>0.1.2</Version>
<Authors>Kamron Batman</Authors> <Authors>Kamron Batman</Authors>
<Company>ModernUO</Company> <Company>ModernUO</Company>
<Product>ModernUO</Product> <Product>ModernUO</Product>
@ -39,20 +39,38 @@
<LangVersion>8.0</LangVersion> <LangVersion>8.0</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="Assemblies\zlib.dll" Condition="'$(RuntimeIdentifier)'=='win-x64' OR !Exists('$(RuntimeIdentifier)')"> <Content Include="Assemblies\libuv.dll" Condition="'$(RuntimeIdentifier)'=='win-x64'">
<Link>libuv.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assemblies\libuv.osx.so" Condition="'$(RuntimeIdentifier)'=='osx-x64'">
<Link>libuv.so</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assemblies\libuv.linux.so" Condition="'$(RuntimeIdentifier)'=='linux-x64'">
<Link>libuv.so</Link>
<Update>libuv.so</Update>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Assemblies\zlib.dll" Condition="'$(RuntimeIdentifier)'=='win-x64'">
<Link>zlib.dll</Link> <Link>zlib.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Assemblies\rdrand.dll" Condition="'$(RuntimeIdentifier)'=='win-x64' OR !Exists('$(RuntimeIdentifier)')"> <Content Include="Assemblies\rdrand.dll" Condition="'$(RuntimeIdentifier)'=='win-x64'">
<Link>rdrand.dll</Link> <Link>rdrand.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Assemblies\rdrand.so" Condition="'$(RuntimeIdentifier)'=='osx-x64' OR '$(RuntimeIdentifier)'=='linux-x64' OR !Exists('$(RuntimeIdentifier)')"> <Content Include="Assemblies\rdrand.so" Condition="'$(RuntimeIdentifier)'=='osx-x64' OR '$(RuntimeIdentifier)'=='linux-x64'">
<Link>rdrand.so</Link> <Link>rdrand.so</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.0.1" />
<PackageReference Include="System.IO.Pipelines" Version="4.6.0" /> <PackageReference Include="System.IO.Pipelines" Version="4.6.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -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.
using System;
using System.Threading.Tasks;
namespace SignalR
{
public static class AsyncDisposableExtensions
{
// Does a light up check to see if a type is IAsyncDisposable and calls DisposeAsync if it is
public static ValueTask DisposeAsync(this IDisposable disposable)
{
if (disposable is IAsyncDisposable asyncDisposable) return asyncDisposable.DisposeAsync();
disposable.Dispose();
return default;
}
}
}