LibUv Networking (#65)
This commit is contained in:
parent
ec6629fbe9
commit
5e2be2d7b6
69 changed files with 5612 additions and 586 deletions
21
.gitignore
vendored
21
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,4 +35,7 @@
|
|||
<IncludeInPackage>false</IncludeInPackage>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -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)];
|
||||
|
||||
|
|
|
|||
108
Projects/Server/ApplicationLifetime.cs
Normal file
108
Projects/Server/ApplicationLifetime.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Projects/Server/Assemblies/libuv.dll
Normal file
BIN
Projects/Server/Assemblies/libuv.dll
Normal file
Binary file not shown.
BIN
Projects/Server/Assemblies/libuv.linux.so
Executable file
BIN
Projects/Server/Assemblies/libuv.linux.so
Executable file
Binary file not shown.
BIN
Projects/Server/Assemblies/libuv.osx.so
Executable file
BIN
Projects/Server/Assemblies/libuv.osx.so
Executable file
Binary file not shown.
132
Projects/Server/Buffers/DiagnosticMemoryPool.cs
Normal file
132
Projects/Server/Buffers/DiagnosticMemoryPool.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
188
Projects/Server/Buffers/DiagnosticPoolBlock.cs
Normal file
188
Projects/Server/Buffers/DiagnosticPoolBlock.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Projects/Server/Buffers/MemoryPoolBlock.cs
Normal file
57
Projects/Server/Buffers/MemoryPoolBlock.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Projects/Server/Buffers/MemoryPoolFactory.cs
Normal file
19
Projects/Server/Buffers/MemoryPoolFactory.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
74
Projects/Server/Buffers/MemoryPoolSlab.cs
Normal file
74
Projects/Server/Buffers/MemoryPoolSlab.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Projects/Server/Buffers/MemoryPoolThrowHelper.cs
Normal file
107
Projects/Server/Buffers/MemoryPoolThrowHelper.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Projects/Server/Buffers/SlabMemoryPool.cs
Normal file
189
Projects/Server/Buffers/SlabMemoryPool.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
43
Projects/Server/Kestrel/CorrelationIdGenerator.cs
Normal file
43
Projects/Server/Kestrel/CorrelationIdGenerator.cs
Normal 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];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()."));
|
||||
}
|
||||
}
|
||||
248
Projects/Server/Kestrel/TransportConnection.Generated.cs
Normal file
248
Projects/Server/Kestrel/TransportConnection.Generated.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
59
Projects/Server/Kestrel/TransportConnection.cs
Normal file
59
Projects/Server/Kestrel/TransportConnection.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Projects/Server/LibUv/IAsyncDisposable.cs
Normal file
12
Projects/Server/LibUv/IAsyncDisposable.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
29
Projects/Server/LibUv/ILibuvTrace.cs
Normal file
29
Projects/Server/LibUv/ILibuvTrace.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
612
Projects/Server/LibUv/Internal/LibuvFunctions.cs
Normal file
612
Projects/Server/LibUv/Internal/LibuvFunctions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Projects/Server/LibUv/Internal/SockAddr.cs
Normal file
107
Projects/Server/LibUv/Internal/SockAddr.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
72
Projects/Server/LibUv/Internal/UvAsyncHandle.cs
Normal file
72
Projects/Server/LibUv/Internal/UvAsyncHandle.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Projects/Server/LibUv/Internal/UvConnectRequest.cs
Normal file
75
Projects/Server/LibUv/Internal/UvConnectRequest.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Projects/Server/LibUv/Internal/UvException.cs
Normal file
14
Projects/Server/LibUv/Internal/UvException.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
66
Projects/Server/LibUv/Internal/UvHandle.cs
Normal file
66
Projects/Server/LibUv/Internal/UvHandle.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Projects/Server/LibUv/Internal/UvLoopHandle.cs
Normal file
54
Projects/Server/LibUv/Internal/UvLoopHandle.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Projects/Server/LibUv/Internal/UvMemory.cs
Normal file
79
Projects/Server/LibUv/Internal/UvMemory.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Projects/Server/LibUv/Internal/UvPipeHandle.cs
Normal file
36
Projects/Server/LibUv/Internal/UvPipeHandle.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
31
Projects/Server/LibUv/Internal/UvRequest.cs
Normal file
31
Projects/Server/LibUv/Internal/UvRequest.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Projects/Server/LibUv/Internal/UvStreamHandle.cs
Normal file
151
Projects/Server/LibUv/Internal/UvStreamHandle.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
72
Projects/Server/LibUv/Internal/UvTcpHandle.cs
Normal file
72
Projects/Server/LibUv/Internal/UvTcpHandle.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Projects/Server/LibUv/Internal/UvTimerHandle.cs
Normal file
56
Projects/Server/LibUv/Internal/UvTimerHandle.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
237
Projects/Server/LibUv/Internal/UvWriteReq.cs
Normal file
237
Projects/Server/LibUv/Internal/UvWriteReq.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Projects/Server/LibUv/LibuvAwaitable.cs
Normal file
79
Projects/Server/LibUv/LibuvAwaitable.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
309
Projects/Server/LibUv/LibuvConnection.cs
Normal file
309
Projects/Server/LibUv/LibuvConnection.cs
Normal 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)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
Projects/Server/LibUv/LibuvConnectionListener.cs
Normal file
196
Projects/Server/LibUv/LibuvConnectionListener.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
98
Projects/Server/LibUv/LibuvConstants.cs
Normal file
98
Projects/Server/LibUv/LibuvConstants.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Projects/Server/LibUv/LibuvOutputConsumer.cs
Normal file
111
Projects/Server/LibUv/LibuvOutputConsumer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
437
Projects/Server/LibUv/LibuvThread.cs
Normal file
437
Projects/Server/LibUv/LibuvThread.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Projects/Server/LibUv/LibuvTrace.cs
Normal file
94
Projects/Server/LibUv/LibuvTrace.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
16
Projects/Server/LibUv/LibuvTransportContext.cs
Normal file
16
Projects/Server/LibUv/LibuvTransportContext.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
51
Projects/Server/LibUv/LibuvTransportOptions.cs
Normal file
51
Projects/Server/LibUv/LibuvTransportOptions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
205
Projects/Server/LibUv/Listener.cs
Normal file
205
Projects/Server/LibUv/Listener.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
168
Projects/Server/LibUv/ListenerContext.cs
Normal file
168
Projects/Server/LibUv/ListenerContext.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
265
Projects/Server/LibUv/ListenerPrimary.cs
Normal file
265
Projects/Server/LibUv/ListenerPrimary.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
Projects/Server/LibUv/ListenerSecondary.cs
Normal file
192
Projects/Server/LibUv/ListenerSecondary.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Projects/Server/LibUv/WriteReqPool.cs
Normal file
63
Projects/Server/LibUv/WriteReqPool.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
42
Projects/Server/Pipelines/DuplexPipe.cs
Normal file
42
Projects/Server/Pipelines/DuplexPipe.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
19
Projects/Server/SignalR/AsyncDisposableExtensions.cs
Normal file
19
Projects/Server/SignalR/AsyncDisposableExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue