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.dll
/Distribution/zlib.dll
# LibUv Dependencies
/Distribution/libuv.dll
/Distribution/libuv.so
/Distribution/Microsoft.AspNetCore.Connections.Abstractions.dll
/Distribution/Microsoft.AspNetCore.Http.Features.dll
/Distribution/Microsoft.Extensions.Configuration.Abstractions.dll
/Distribution/Microsoft.Extensions.Configuration.Binder.dll
/Distribution/Microsoft.Extensions.Configuration.dll
/Distribution/Microsoft.Extensions.DependencyInjection.Abstractions.dll
/Distribution/Microsoft.Extensions.DependencyInjection.dll
/Distribution/Microsoft.Extensions.FileProviders.Abstractions.dll
/Distribution/Microsoft.Extensions.Hosting.Abstractions.dll
/Distribution/Microsoft.Extensions.Logging.Abstractions.dll
/Distribution/Microsoft.Extensions.Logging.Configuration.dll
/Distribution/Microsoft.Extensions.Logging.Console.dll
/Distribution/Microsoft.Extensions.Logging.dll
/Distribution/Microsoft.Extensions.Options.ConfigurationExtensions.dll
/Distribution/Microsoft.Extensions.Options.dll
/Distribution/Microsoft.Extensions.Primitives.dll
/Projects/Scripts/obj
/Projects/Scripts/bin
/Projects/Server/obj

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).
## Reporting security issues and bugs
Security issues and bugs should be reported privately, via email, to the hi@modernuo.com.
Security issues and bugs should be reported privately, via email, to hi@modernuo.com.
You should receive a response within 24 hours.
If for some reason you do not, please follow up via email to ensure we received your original message.
@ -35,7 +35,7 @@ If you don't know what a pull request is read this article: https://help.github.
configurations, environment variables, dependencies, etc.
1. Increase the version numbers to the new version that this Pull Request would represent.
The versioning scheme we use is [SemVer](http://semver.org/).
1. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
1. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
## Code of Conduct

View file

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

View file

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

View file

@ -3,24 +3,25 @@ using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using Microsoft.AspNetCore.Connections;
using Server.Network;
namespace Server.Misc
{
public class ServerList
{
/*
/*
* The default setting for Address, a value of 'null', will use your local IP address. If all of your local IP addresses
* are private network addresses and AutoDetect is 'true' then RunUO will attempt to discover your public IP address
* for you automatically.
*
* If you do not plan on allowing clients outside of your LAN to connect, you can set AutoDetect to 'false' and leave
* Address set to 'null'.
*
*
* If your public IP address cannot be determined, you must change the value of Address to your public IP address
* manually to allow clients outside of your LAN to connect to your server. Address can be either an IP address or
* a hostname that will be resolved when RunUO starts.
*
*
* If you want players outside your LAN to be able to connect to your server and you are behind a router, you must also
* forward TCP port 2593 to your private IP address. The procedure for doing this varies by manufacturer but generally
* involves configuration of the router through your web browser.
@ -32,7 +33,7 @@ namespace Server.Misc
* properly and fully supports listening on multiple ports. If a client with a public IP address is connecting to a
* locally private address, the server will direct the client to either the AutoDetected IP address or the manually entered
* IP address or hostname, whichever is applicable. Loopback clients will be directed to loopback.
*
*
* If you would like to listen on additional ports (i.e. 22, 23, 80, for clients behind highly restrictive egress
* firewalls) or specific IP adddresses you can do so by modifying the file SocketOptions.cs found in this directory.
*/
@ -64,7 +65,7 @@ namespace Server.Misc
try
{
NetState ns = e.State;
Socket s = ns.Socket;
ConnectionContext s = ns.Connection;
IPEndPoint ipep = (IPEndPoint)s.LocalEndPoint;

View file

@ -5,8 +5,6 @@ namespace Server
{
public class SocketOptions
{
private const bool NagleEnabled = false; // Should the Nagle algorithm be enabled? This may reduce performance
private static IPEndPoint[] m_ListenerEndPoints =
{
new IPEndPoint(IPAddress.Any, 2593) // Default: Listen on port 2593 on all IP addresses
@ -16,24 +14,10 @@ namespace Server
// new IPEndPoint( IPAddress.Parse( "1.2.3.4" ), 2593 ), // Listen on port 2593 on IP address 1.2.3.4
};
public static void Initialize()
{
EventSink.SocketConnect += EventSink_SocketConnect;
}
public static void RegisterListeners()
{
for (int i = 0; i < m_ListenerEndPoints.Length; i++)
Core.MessagePump.AddListener(m_ListenerEndPoints[i]);
}
private static void EventSink_SocketConnect(SocketConnectEventArgs e)
{
if (!e.AllowConnection)
return;
if (!NagleEnabled)
e.Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1); // RunUO uses its own algorithm
}
}
}

View file

@ -35,4 +35,7 @@
<IncludeInPackage>false</IncludeInPackage>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.0.0" />
</ItemGroup>
</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)];

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

View file

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

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

View file

@ -1445,7 +1445,7 @@ namespace Server
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)]
public NetState NetState
{
get => m_NetState?.Socket != null && !m_NetState.IsDisposing ? m_NetState : null;
get => m_NetState?.Connection != null && !m_NetState.IsDisposing ? m_NetState : null;
set
{
if (m_NetState != value)

View file

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

View file

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

View file

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

View file

@ -28,7 +28,7 @@ namespace Server.Network
public class PacketHandler
{
public PacketHandler(int packetID, long length, bool ingame, OnPacketReceive onReceive)
public PacketHandler(int packetID, int length, bool ingame, OnPacketReceive onReceive)
{
PacketID = packetID;
Length = length;
@ -38,7 +38,7 @@ namespace Server.Network
public int PacketID{ get; }
public long Length{ get; }
public int Length{ get; }
public OnPacketReceive OnReceive{ get; }

View file

@ -109,43 +109,32 @@ namespace Server.Network
Register(0x01, 5, false, Disconnect);
Register(0x02, 7, true, MovementReq);
Register(0x03, 0, true, AsciiSpeech);
Register(0x04, 2, true, GodModeRequest);
Register(0x05, 5, true, AttackReq);
Register(0x06, 5, true, UseReq);
Register(0x07, 7, true, LiftReq);
Register(0x08, 14, true, DropReq);
Register(0x09, 5, true, LookReq);
Register(0x0A, 11, true, Edit);
Register(0x12, 0, true, TextCommand);
Register(0x13, 10, true, EquipReq);
Register(0x14, 6, true, ChangeZ);
Register(0x22, 3, true, Resynchronize);
Register(0x2C, 2, true, DeathStatusResponse);
Register(0x34, 10, true, MobileQuery);
Register(0x3A, 0, true, ChangeSkillLock);
Register(0x3B, 0, true, VendorBuyReply);
Register(0x47, 11, true, NewTerrain);
Register(0x48, 73, true, NewAnimData);
Register(0x58, 106, true, NewRegion);
Register(0x5D, 73, false, PlayCharacter);
Register(0x61, 9, true, DeleteStatic);
Register(0x6C, 19, true, TargetResponse);
Register(0x6F, 0, true, SecureTrade);
Register(0x72, 5, true, SetWarMode);
Register(0x73, 2, false, PingReq);
Register(0x75, 35, true, RenameRequest);
Register(0x79, 9, true, ResourceQuery);
Register(0x7E, 2, true, GodviewQuery);
Register(0x7D, 13, true, MenuResponse);
Register(0x80, 62, false, AccountLogin);
Register(0x83, 39, false, DeleteCharacter);
Register(0x91, 65, false, GameLogin);
Register(0x95, 9, true, HuePickerResponse);
Register(0x96, 0, true, GameCentralMoniter);
Register(0x98, 0, true, MobileNameRequest);
Register(0x9A, 0, true, AsciiPromptResponse);
Register(0x9B, 258, true, HelpRequest);
Register(0x9D, 51, true, GMSingle);
Register(0x9F, 0, true, VendorSellReply);
Register(0xA0, 3, false, PlayServer);
Register(0xA4, 149, false, SystemInfo);
@ -161,8 +150,6 @@ namespace Server.Network
Register(0xBF, 0, true, ExtendedCommand);
Register(0xC2, 0, true, UnicodePromptResponse);
Register(0xC8, 2, true, SetUpdateRange);
Register(0xC9, 6, true, TripTime);
Register(0xCA, 6, true, UTripTime);
Register(0xCF, 0, false, AccountLogin);
Register(0xD0, 0, true, ConfigurationFile);
Register(0xD1, 2, true, LogoutReq);
@ -172,6 +159,7 @@ namespace Server.Network
Register(0xEF, 21, false, LoginServerSeed);
Register(0xF4, 0, false, CrashReport);
Register(0xF8, 106, false, CreateCharacter70160);
Register(0xFB, 2, false, ShowPublicHouseContent);
Register6017(0x08, 15, true, DropReq6017);
@ -214,9 +202,7 @@ namespace Server.Network
public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive)
{
Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
if (m_6017Handlers[packetID] == null)
m_6017Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive);
m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive);
}
public static PacketHandler GetHandler(int packetID) => Handlers[packetID];
@ -291,7 +277,9 @@ namespace Server.Network
ph.ThrottleCallback = t;
}
public static long ProcessPacket(MessagePump pump, NetState ns, in ReadOnlySequence<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);
@ -342,7 +330,7 @@ namespace Server.Network
return -1;
}
long packetLength = handler.Length;
int packetLength = handler.Length;
if (handler.Length <= 0 && r.Length >= 3)
{
packetLength = r.ReadUInt16();
@ -373,16 +361,12 @@ namespace Server.Network
if (throttled > TimeSpan.Zero)
ns.ThrottledUntil = DateTime.UtcNow + throttled;
PacketReceiveProfile prof = null;
ReadOnlySequence<byte> packet = seq.Slice(r.Position);
IMemoryOwner<byte> memOwner = _memoryPool.Rent((int)packet.Length);
if (Core.Profiling)
prof = PacketReceiveProfile.Acquire(packetId);
packet.CopyTo(memOwner.Memory.Span);
prof?.Start();
pump.QueueWork(ns, seq.Slice(r.Position), handler.OnReceive);
prof?.Finish(packetLength);
pump.QueueWork(ns, memOwner, handler.OnReceive);
return packetLength;
}
@ -413,9 +397,9 @@ namespace Server.Network
public static void EncodedCommand(NetState state, PacketReader pvSrc)
{
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)
{
@ -423,7 +407,7 @@ namespace Server.Network
{
Console.WriteLine(
"Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile", state,
packetID);
packetId);
state.Dispose();
}
else if (ph.Ingame && state.Mobile.Deleted)
@ -609,39 +593,6 @@ namespace Server.Network
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)
{
// Ignored
@ -711,35 +662,7 @@ namespace Server.Network
huePicker.OnResponse(hue);
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)
@ -758,101 +681,10 @@ namespace Server.Network
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 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)
{
int type = pvSrc.ReadByte();
@ -862,34 +694,6 @@ namespace Server.Network
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
{
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)
{
uint serial = pvSrc.ReadUInt32();
@ -1550,9 +1348,7 @@ namespace Server.Network
uint value = pvSrc.ReadUInt32();
if ((value & ~0x7FFFFFFF) != 0)
{
from.OnPaperdollRequest();
}
else
{
Serial s = value;
@ -1576,9 +1372,7 @@ namespace Server.Network
from.NextActionTime = Core.TickCount + Mobile.ActionDelay;
}
else
{
from.SendActionMessage();
}
}
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 (SingleClickProps)
{
m.OnAosSingleClick(from);
}
else
{
if (from.Region.OnSingleClick(from, m))
@ -1612,9 +1404,7 @@ namespace Server.Network
Utility.InUpdateRange(from.Location, item.GetWorldLocation()))
{
if (SingleClickProps)
{
item.OnAosSingleClick(from);
}
else if (from.Region.OnSingleClick(from, item))
{
if (item.Parent is Item item1)
@ -2062,13 +1852,6 @@ namespace Server.Network
if (m != null)
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
{
m.OnStatsQuery(from);
@ -2094,44 +1877,10 @@ namespace Server.Network
string name = pvSrc.ReadString(30);
pvSrc.Seek(2, SeekOrigin.Current);
int flags = pvSrc.ReadInt32();
/* if (FeatureProtection.DisabledFeatures != 0 && ThirdPartyAuthCallback != null)
{
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;
pvSrc.Seek(24, SeekOrigin.Current);
int charSlot = 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)
{
state.Send(new LoginConfirm(m));
@ -2614,9 +2368,7 @@ namespace Server.Network
state.Send(new CharacterListOld(state.Account, state.CityInfo));
}
else
{
state.Dispose();
}
}
public static void PlayServer(NetState state, PacketReader pvSrc)
@ -2626,9 +2378,7 @@ namespace Server.Network
IAccount a = state.Account;
if (info == null || a == null || index < 0 || index >= info.Length)
{
state.Dispose();
}
else
{
ServerInfo si = info[index];

View file

@ -175,13 +175,13 @@ namespace Server.Network
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)
{
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);
else
{
@ -203,7 +203,7 @@ namespace Server.Network
{
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);
else
{
@ -222,7 +222,7 @@ namespace Server.Network
public string ReadUTF8String() =>
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()
);

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
{
internal static int m_AuthID = -1;

View file

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

View file

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