Removes libdrng (rdrand) (#139)
This commit is contained in:
parent
739e77a973
commit
2770ab1d50
42 changed files with 64 additions and 4298 deletions
Binary file not shown.
|
|
@ -1,12 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
interface IAsyncDisposable
|
||||
{
|
||||
Task DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct UvWriteResult
|
||||
{
|
||||
public int Status { get; }
|
||||
public UvException Error { get; }
|
||||
|
||||
public UvWriteResult(int status, UvException error)
|
||||
{
|
||||
Status = status;
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,313 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal partial class LibuvConnection : TransportConnection
|
||||
{
|
||||
private static readonly int MinAllocBufferSize = SlabMemoryPool.BlockSize / 2;
|
||||
|
||||
private static readonly Action<UvStreamHandle, int, object> _readCallback =
|
||||
(handle, status, state) => ReadCallback(handle, status, state);
|
||||
|
||||
private static readonly Func<UvStreamHandle, int, object, LibuvFunctions.uv_buf_t> _allocCallback =
|
||||
(handle, suggestedSize, state) => AllocCallback(handle, suggestedSize, state);
|
||||
|
||||
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,
|
||||
preferLocal: 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);
|
||||
}
|
||||
else if (LibuvConstants.IsConnectionReset(uvError.StatusCode))
|
||||
{
|
||||
// Log connection resets at a lower (Debug) level.
|
||||
Log.ConnectionReset(ConnectionId);
|
||||
return new ConnectionResetException(uvError.Message, uvError);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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();
|
||||
}
|
||||
|
||||
internal 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 = (Libuv.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, var 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// 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 System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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 (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return -4077;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -104;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -54;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetEPIPE()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return -4047;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -32;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -32;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetENOTCONN()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return -4053;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -107;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -57;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetEINVAL()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return -4071;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -22;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -22;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetEADDRINUSE()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return -4091;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -98;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -48;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetENOTSUP()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -95;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -45;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetECANCELED()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return -4081;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return -125;
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return -89;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,415 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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 = false;
|
||||
private bool _initCompleted = false;
|
||||
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; }
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal class LibuvTransportContext
|
||||
{
|
||||
public LibuvTransportOptions Options { get; set; }
|
||||
|
||||
public IHostApplicationLifetime AppLifetime { get; set; }
|
||||
|
||||
public ILibuvTrace Log { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal class LibuvTransportFactory : IConnectionListenerFactory
|
||||
{
|
||||
private readonly LibuvTransportContext _baseTransportContext;
|
||||
|
||||
public LibuvTransportFactory(
|
||||
IOptions<LibuvTransportOptions> options,
|
||||
IHostApplicationLifetime applicationLifetime,
|
||||
ILoggerFactory loggerFactory)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
if (applicationLifetime == null) throw new ArgumentNullException(nameof(applicationLifetime));
|
||||
if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory));
|
||||
|
||||
var logger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv");
|
||||
var trace = new LibuvTrace(logger);
|
||||
|
||||
var threadCount = options.Value.ThreadCount;
|
||||
|
||||
if (threadCount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(threadCount),
|
||||
threadCount,
|
||||
"ThreadCount must be positive.");
|
||||
|
||||
if (!LibuvConstants.ECONNRESET.HasValue) trace.LogWarning("Unable to determine ECONNRESET value on this platform.");
|
||||
|
||||
if (!LibuvConstants.EADDRINUSE.HasValue) trace.LogWarning("Unable to determine EADDRINUSE value on this platform.");
|
||||
|
||||
_baseTransportContext = new LibuvTransportContext
|
||||
{
|
||||
Options = options.Value,
|
||||
AppLifetime = applicationLifetime,
|
||||
Log = trace,
|
||||
};
|
||||
}
|
||||
|
||||
public async ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var transportContext = new LibuvTransportContext
|
||||
{
|
||||
Options = _baseTransportContext.Options,
|
||||
AppLifetime = _baseTransportContext.AppLifetime,
|
||||
Log = _baseTransportContext.Log
|
||||
};
|
||||
|
||||
var transport = new LibuvConnectionListener(transportContext, endpoint);
|
||||
await transport.BindAsync();
|
||||
return transport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for listeners in Kestrel. Listens for incoming connections
|
||||
/// </summary>
|
||||
internal 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()
|
||||
{
|
||||
switch (EndPoint)
|
||||
{
|
||||
case IPEndPoint _:
|
||||
return ListenTcp(useFileHandle: false);
|
||||
case UnixDomainSocketEndPoint _:
|
||||
return ListenPipe(useFileHandle: false);
|
||||
case FileHandleEndPoint _:
|
||||
return ListenHandle();
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private UvTcpHandle ListenTcp(bool useFileHandle)
|
||||
{
|
||||
var socket = new UvTcpHandle(Log);
|
||||
|
||||
try
|
||||
{
|
||||
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
|
||||
socket.NoDelay(TransportContext.Options.NoDelay);
|
||||
|
||||
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(useFileHandle: true);
|
||||
case FileHandleType.Pipe:
|
||||
return ListenPipe(useFileHandle: true);
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
UvStreamHandle handle;
|
||||
try
|
||||
{
|
||||
handle = ListenTcp(useFileHandle: 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(useFileHandle: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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(TransportContext.Options.NoDelay);
|
||||
}
|
||||
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;
|
||||
|
||||
return fileHandleEndPoint.FileHandleType switch
|
||||
{
|
||||
FileHandleType.Auto => throw new InvalidOperationException(
|
||||
"Cannot accept on a non-specific file handle, listen should be performed first."),
|
||||
FileHandleType.Tcp => (UvStreamHandle)AcceptTcp(),
|
||||
FileHandleType.Pipe => AcceptPipe(),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,264 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A primary listener waits for incoming connections on a specified socket. Incoming
|
||||
/// connections may be passed to a secondary listener to handle.
|
||||
/// </summary>
|
||||
internal 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 = PlatformApis.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
|
||||
{
|
||||
#pragma warning disable 169
|
||||
uint status;
|
||||
ulong information;
|
||||
#pragma warning restore 169
|
||||
}
|
||||
|
||||
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/dotnet/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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A secondary listener is delegated requests from a primary listener via a named pipe or
|
||||
/// UNIX domain socket.
|
||||
/// </summary>
|
||||
internal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,619 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal class LibuvFunctions
|
||||
{
|
||||
public LibuvFunctions()
|
||||
{
|
||||
IsWindows = PlatformApis.IsWindows;
|
||||
|
||||
_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 readonly bool IsWindows;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private UvException GetError(int statusCode)
|
||||
{
|
||||
// Note: method marked as NoInlining so it doesn't bloat either of the two preceding functions
|
||||
// Check and ThrowError and alter their jit heuristics.
|
||||
var errorName = err_name(statusCode);
|
||||
var errorDescription = strerror(statusCode);
|
||||
return new UvException("Error " + statusCode + " " + errorName + " " + errorDescription, statusCode);
|
||||
}
|
||||
|
||||
protected Func<UvLoopHandle, int> _uv_loop_init;
|
||||
public void loop_init(UvLoopHandle handle)
|
||||
{
|
||||
ThrowIfErrored(_uv_loop_init(handle));
|
||||
}
|
||||
|
||||
protected Func<IntPtr, int> _uv_loop_close;
|
||||
public void loop_close(UvLoopHandle handle)
|
||||
{
|
||||
handle.Validate(closed: true);
|
||||
ThrowIfErrored(_uv_loop_close(handle.InternalGetHandle()));
|
||||
}
|
||||
|
||||
protected Func<UvLoopHandle, int, int> _uv_run;
|
||||
public void run(UvLoopHandle handle, int mode)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_run(handle, mode));
|
||||
}
|
||||
|
||||
protected Action<UvLoopHandle> _uv_stop;
|
||||
public void stop(UvLoopHandle handle)
|
||||
{
|
||||
handle.Validate();
|
||||
_uv_stop(handle);
|
||||
}
|
||||
|
||||
protected Action<UvHandle> _uv_ref;
|
||||
public void @ref(UvHandle handle)
|
||||
{
|
||||
handle.Validate();
|
||||
_uv_ref(handle);
|
||||
}
|
||||
|
||||
protected Action<UvHandle> _uv_unref;
|
||||
public void unref(UvHandle handle)
|
||||
{
|
||||
handle.Validate();
|
||||
_uv_unref(handle);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
protected delegate int uv_fileno_func(UvHandle handle, ref IntPtr socket);
|
||||
protected 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);
|
||||
protected Action<IntPtr, uv_close_cb> _uv_close;
|
||||
public void close(UvHandle handle, uv_close_cb close_cb)
|
||||
{
|
||||
handle.Validate(closed: 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);
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected Func<UvAsyncHandle, int> _uv_async_send;
|
||||
public void async_send(UvAsyncHandle handle)
|
||||
{
|
||||
ThrowIfErrored(_uv_async_send(handle));
|
||||
}
|
||||
|
||||
protected Func<IntPtr, int> _uv_unsafe_async_send;
|
||||
public void unsafe_async_send(IntPtr handle)
|
||||
{
|
||||
ThrowIfErrored(_uv_unsafe_async_send(handle));
|
||||
}
|
||||
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected delegate int uv_tcp_bind_func(UvTcpHandle handle, ref SockAddr addr, int flags);
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected Func<UvTcpHandle, IntPtr, int> _uv_tcp_open;
|
||||
public void tcp_open(UvTcpHandle handle, IntPtr hSocket)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_tcp_open(handle, hSocket));
|
||||
}
|
||||
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected Func<UvPipeHandle, string, int> _uv_pipe_bind;
|
||||
public void pipe_bind(UvPipeHandle handle, string name)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_pipe_bind(handle, name));
|
||||
}
|
||||
|
||||
protected 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);
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected 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);
|
||||
protected 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);
|
||||
}
|
||||
|
||||
protected 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);
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected Func<UvStreamHandle, int> _uv_read_stop;
|
||||
public void read_stop(UvStreamHandle handle)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_read_stop(handle));
|
||||
}
|
||||
|
||||
protected 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);
|
||||
|
||||
protected unsafe delegate int uv_write_func(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb);
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected unsafe delegate int uv_write2_func(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, UvStreamHandle sendHandle, uv_write_cb cb);
|
||||
protected 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));
|
||||
}
|
||||
|
||||
protected 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);
|
||||
}
|
||||
|
||||
protected Func<int, IntPtr> _uv_strerror;
|
||||
public string strerror(int err)
|
||||
{
|
||||
IntPtr ptr = _uv_strerror(err);
|
||||
return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr);
|
||||
}
|
||||
|
||||
protected Func<int> _uv_loop_size;
|
||||
public int loop_size() => _uv_loop_size();
|
||||
|
||||
protected Func<HandleType, int> _uv_handle_size;
|
||||
public int handle_size(HandleType handleType) => _uv_handle_size(handleType);
|
||||
|
||||
protected Func<RequestType, int> _uv_req_size;
|
||||
public int req_size(RequestType reqType) => _uv_req_size(reqType);
|
||||
|
||||
protected delegate int uv_ip4_addr_func(string ip, int port, out SockAddr addr);
|
||||
protected 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);
|
||||
}
|
||||
|
||||
protected delegate int uv_ip6_addr_func(string ip, int port, out SockAddr addr);
|
||||
protected 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);
|
||||
protected 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);
|
||||
}
|
||||
|
||||
protected Func<UvLoopHandle, UvTimerHandle, int> _uv_timer_init;
|
||||
public unsafe 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);
|
||||
protected Func<UvTimerHandle, uv_timer_cb, long, long, int> _uv_timer_start;
|
||||
public unsafe void timer_start(UvTimerHandle handle, uv_timer_cb cb, long timeout, long repeat)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_timer_start(handle, cb, timeout, repeat));
|
||||
}
|
||||
|
||||
protected Func<UvTimerHandle, int> _uv_timer_stop;
|
||||
public unsafe void timer_stop(UvTimerHandle handle)
|
||||
{
|
||||
handle.Validate();
|
||||
ThrowIfErrored(_uv_timer_stop(handle));
|
||||
}
|
||||
|
||||
protected Func<UvLoopHandle, long> _uv_now;
|
||||
public unsafe 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);
|
||||
protected 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);
|
||||
protected 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, 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 IsWindows)
|
||||
{
|
||||
if (IsWindows)
|
||||
{
|
||||
_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 unsafe int uv_timer_init(UvLoopHandle loop, UvTimerHandle handle);
|
||||
|
||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern unsafe int uv_timer_start(UvTimerHandle handle, uv_timer_cb cb, long timeout, long repeat);
|
||||
|
||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern unsafe int uv_timer_stop(UvTimerHandle handle);
|
||||
|
||||
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern unsafe 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// 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 System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal static class PlatformApis
|
||||
{
|
||||
public static bool IsWindows { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
public static bool IsDarwin { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long VolatileRead(ref long value) => IntPtr.Size == 8 ? Volatile.Read(ref value) : Interlocked.Read(ref value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal 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 readonly long _field0;
|
||||
private readonly long _field1;
|
||||
private readonly 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 (PlatformApis.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);
|
||||
}
|
||||
else if (IsIPv4MappedToIPv6())
|
||||
{
|
||||
var ipv4bits = (_field2 >> 32) & 0x00000000FFFFFFFF;
|
||||
return new IPEndPoint(new IPAddress(ipv4bits), port);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsIPv4MappedToIPv6()
|
||||
{
|
||||
// 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
|
||||
if (_field1 != 0) return false;
|
||||
|
||||
return (_field2 & 0xFFFFFFFF) == 0xFFFF0000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal 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".
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for UvWriteRequest
|
||||
/// </summary>
|
||||
internal class UvConnectRequest : UvRequest
|
||||
{
|
||||
private static readonly LibuvFunctions.uv_connect_cb _uv_connect_cb = (req, status) => UvConnectCb(req, status);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal class UvException : Exception
|
||||
{
|
||||
public UvException(string message, int statusCode) : base(message) => StatusCode = statusCode;
|
||||
|
||||
public int StatusCode { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal abstract class UvHandle : UvMemory
|
||||
{
|
||||
private static readonly LibuvFunctions.uv_close_cb _destroyMemory = (handle) => DestroyMemory(handle);
|
||||
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()
|
||||
{
|
||||
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".
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
// 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.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for UvMemory
|
||||
/// </summary>
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal class UvRequest : UvMemory
|
||||
{
|
||||
protected UvRequest(ILibuvTrace logger) : base(logger, GCHandleType.Normal)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Init(LibuvThread thread)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
DestroyMemory(handle);
|
||||
handle = IntPtr.Zero;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal abstract class UvStreamHandle : UvHandle
|
||||
{
|
||||
private static readonly LibuvFunctions.uv_connection_cb _uv_connection_cb = (handle, status) => UvConnectionCb(handle, status);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for UvWriteRequest
|
||||
/// </summary>
|
||||
internal class UvWriteReq : UvRequest
|
||||
{
|
||||
private static readonly LibuvFunctions.uv_write_cb _uv_write_cb = (IntPtr ptr, int status) => UvWriteCb(ptr, status);
|
||||
|
||||
private IntPtr _bufs;
|
||||
|
||||
private Action<UvWriteReq, int, UvException, object> _callback;
|
||||
private object _state;
|
||||
private const int BUFFER_COUNT = 4;
|
||||
|
||||
private readonly LibuvAwaitable<UvWriteReq> _awaitable = new LibuvAwaitable<UvWriteReq>();
|
||||
private readonly List<GCHandle> _pins = new List<GCHandle>(BUFFER_COUNT + 1);
|
||||
private readonly 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, sendHandle: null, callback: callback, state: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||
{
|
||||
internal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Server.Kestrel.Transport.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>
|
||||
/// Set to false to enable Nagle's algorithm for all connections.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Defaults to true.
|
||||
/// </remarks>
|
||||
public bool NoDelay { get; set; } = true;
|
||||
|
||||
/// <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 to do webserving).
|
||||
var threadCount = Environment.ProcessorCount >> 1;
|
||||
|
||||
if (threadCount < 1)
|
||||
// Ensure shifted value is at least one
|
||||
return 1;
|
||||
|
||||
if (threadCount > 16)
|
||||
// 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 16;
|
||||
|
||||
return threadCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// 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.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Hosting
|
||||
{
|
||||
public static class WebHostBuilderLibuvExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Specify Libuv as the transport to be used by Kestrel.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">
|
||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
|
||||
/// </returns>
|
||||
public static IWebHostBuilder UseLibuv(this IWebHostBuilder hostBuilder)
|
||||
{
|
||||
return hostBuilder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IConnectionListenerFactory, LibuvTransportFactory>();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify Libuv as the transport to be used by Kestrel.
|
||||
/// </summary>
|
||||
/// <param name="hostBuilder">
|
||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure.
|
||||
/// </param>
|
||||
/// <param name="configureOptions">
|
||||
/// A callback to configure Libuv options.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The Microsoft.AspNetCore.Hosting.IWebHostBuilder.
|
||||
/// </returns>
|
||||
public static IWebHostBuilder UseLibuv(this IWebHostBuilder hostBuilder, Action<LibuvTransportOptions> configureOptions)
|
||||
{
|
||||
return hostBuilder.UseLibuv().ConfigureServices(services =>
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -357,9 +357,6 @@ namespace Server
|
|||
Console.WriteLine("Core: High resolution timing ({0})",
|
||||
Stopwatch.IsHighResolution ? "Supported" : "Unsupported");
|
||||
|
||||
Console.WriteLine("SecureRandomImpl: {0} ({1})", SecureRandomImpl.Name,
|
||||
SecureRandomImpl.IsHardwareRNG ? "Hardware" : "Software");
|
||||
|
||||
ServerConfiguration.Load();
|
||||
|
||||
// Load UOContent.dll
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@
|
|||
<Delete Files="..\..\Distribution\zlib.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libz.dylib" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libz.so" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\libdrng.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Microsoft.AspNetCore.Connections.Abstractions.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Microsoft.AspNetCore.Http.Features.dll" ContinueOnError="true" />
|
||||
<Delete Files="..\..\Distribution\Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.dll" ContinueOnError="true" />
|
||||
|
|
@ -75,12 +74,6 @@
|
|||
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
|
||||
<CodeAnalysisRuleSet>..\..\Rules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Assemblies\libdrng.dll" Condition="$(DefineConstants.Contains('WINDOWS'))">
|
||||
<Link>libdrng.dll</Link>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="3.1.3" />
|
||||
|
|
@ -88,15 +81,6 @@
|
|||
<PackageReference Include="System.IO.Pipelines" Version="4.7.1" />
|
||||
<PackageReference Include="Zlib.Bindings" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Kestrel\Transport.Libuv\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Kestrel\Transport.Libuv\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Kestrel\Transport.Libuv\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<Version>1.1.118</Version>
|
||||
|
|
|
|||
|
|
@ -1,115 +0,0 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: DRng64.cs - Created: 2019/12/30 - Updated: 2019/12/30 *
|
||||
* *
|
||||
* 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.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public sealed class DRng64 : RandomNumberGenerator, IHardwareRNG
|
||||
{
|
||||
public enum RDRandError
|
||||
{
|
||||
Unknown = -4,
|
||||
Unsupported = -3,
|
||||
Supported = -2,
|
||||
NotReady = -1,
|
||||
|
||||
Failure = 0,
|
||||
|
||||
Success = 1
|
||||
}
|
||||
|
||||
public bool IsSupported()
|
||||
{
|
||||
ulong r = 0;
|
||||
return SafeNativeMethods.rdrand_64(ref r, true) == RDRandError.Success;
|
||||
}
|
||||
|
||||
private static unsafe void GetBytes(byte* pBuffer, int count)
|
||||
{
|
||||
SafeNativeMethods.rdrand_get_bytes(count, pBuffer);
|
||||
}
|
||||
|
||||
public override void GetBytes(byte[] data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
GetBytes(new Span<byte>(data));
|
||||
}
|
||||
|
||||
public override void GetBytes(byte[] data, int offset, int count)
|
||||
{
|
||||
GetBytes(new Span<byte>(data, offset, count));
|
||||
}
|
||||
|
||||
public override unsafe void GetBytes(Span<byte> data)
|
||||
{
|
||||
if (data.Length > 0)
|
||||
fixed (byte* ptr = data)
|
||||
{
|
||||
GetBytes(ptr, data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetNonZeroBytes(byte[] data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
GetNonZeroBytes(new Span<byte>(data));
|
||||
}
|
||||
|
||||
public override void GetNonZeroBytes(Span<byte> data)
|
||||
{
|
||||
while (data.Length > 0)
|
||||
{
|
||||
// Fill the remaining portion of the span with random bytes.
|
||||
GetBytes(data);
|
||||
|
||||
// Find the first zero in the remaining portion.
|
||||
var indexOfFirst0Byte = data.Length;
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
if (data[i] == 0)
|
||||
{
|
||||
indexOfFirst0Byte = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// If there were any zeros, shift down all non-zeros.
|
||||
for (var i = indexOfFirst0Byte + 1; i < data.Length; i++)
|
||||
if (data[i] != 0)
|
||||
data[indexOfFirst0Byte++] = data[i];
|
||||
|
||||
// Request new random bytes if necessary; dont re-use
|
||||
// existing bytes since they were shifted down.
|
||||
data = data.Slice(indexOfFirst0Byte);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SafeNativeMethods
|
||||
{
|
||||
[DllImport("libdrng", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern RDRandError rdrand_64(ref ulong rand, bool retry);
|
||||
|
||||
[DllImport("libdrng", CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern unsafe RDRandError rdrand_get_bytes(int n, byte* buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Projects/Server/Utilities/RandomProviders.cs
Normal file
42
Projects/Server/Utilities/RandomProviders.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: RandomProviders.cs - Created: 2020/05/24 - Updated: 2020/05/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.Security.Cryptography;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public interface IRandomProvider
|
||||
{
|
||||
public uint Next(uint c);
|
||||
public bool NextBool();
|
||||
public void GetBytes(Span<byte> b);
|
||||
public double NextDouble();
|
||||
}
|
||||
|
||||
public static class RandomProviders
|
||||
{
|
||||
private static IRandomProvider m_Provider;
|
||||
private static IRandomProvider m_SecureProvider;
|
||||
|
||||
public static IRandomProvider Provider => m_Provider ??= new Xoshiro256PlusPlus();
|
||||
public static IRandomProvider SecureProvider => m_SecureProvider ??= new SecureRandom();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: Random.cs - Created: 2019/12/30 - Updated: 2019/01/05 *
|
||||
* File: Random.cs - Created: 2019/12/30 - Updated: 2020/05/23 *
|
||||
* *
|
||||
* 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 *
|
||||
|
|
@ -23,49 +23,18 @@ using System.Security.Cryptography;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles random number generation.
|
||||
/// </summary>
|
||||
public static class RandomImpl
|
||||
public class SecureRandom : RandomNumberGenerator, IRandomProvider
|
||||
{
|
||||
private static readonly Xoshiro256PlusPlus _Random = new Xoshiro256PlusPlus();
|
||||
private readonly RandomNumberGenerator m_Random = new RNGCryptoServiceProvider();
|
||||
|
||||
public static uint Next(uint c) => _Random.Next(c);
|
||||
public override void GetBytes(byte[] data) => m_Random.GetBytes(data);
|
||||
|
||||
public static bool NextBool() => _Random.NextBool();
|
||||
public override void GetBytes(Span<byte> data) => m_Random.GetBytes(data);
|
||||
|
||||
public static void GetBytes(Span<byte> b) => _Random.GetBytes(b);
|
||||
public uint Next(uint c) => throw new NotImplementedException();
|
||||
|
||||
public static double NextDouble() => _Random.NextDouble();
|
||||
}
|
||||
public bool NextBool() => throw new NotImplementedException();
|
||||
|
||||
public static class SecureRandomImpl
|
||||
{
|
||||
public static readonly RandomNumberGenerator _Random;
|
||||
|
||||
static SecureRandomImpl()
|
||||
{
|
||||
try
|
||||
{
|
||||
_Random = new DRng64();
|
||||
if (_Random is IHardwareRNG rng && rng?.IsSupported() != true)
|
||||
_Random = new RNGCryptoServiceProvider();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_Random = new RNGCryptoServiceProvider();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsHardwareRNG => _Random is IHardwareRNG;
|
||||
|
||||
public static string Name => _Random.GetType().Name;
|
||||
|
||||
public static void GetBytes(Span<byte> buffer) => _Random.GetBytes(buffer);
|
||||
}
|
||||
|
||||
public interface IHardwareRNG
|
||||
{
|
||||
bool IsSupported();
|
||||
public double NextDouble() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -875,7 +875,7 @@ namespace Server
|
|||
var sides = numSides;
|
||||
|
||||
for (var i = 0; i < numDice; ++i)
|
||||
total += (int)RandomImpl.Next(sides) + 1;
|
||||
total += (int)RandomProviders.Provider.Next(sides) + 1;
|
||||
|
||||
return total + bonus;
|
||||
}
|
||||
|
|
@ -885,7 +885,7 @@ namespace Server
|
|||
var count = list.Count;
|
||||
for (var i = count - 1; i > 0; i--)
|
||||
{
|
||||
var r = (int)RandomImpl.Next((uint)count);
|
||||
var r = (int)RandomProviders.Provider.Next((uint)count);
|
||||
var swap = list[r];
|
||||
list[r] = list[i];
|
||||
list[i] = swap;
|
||||
|
|
@ -896,7 +896,7 @@ namespace Server
|
|||
|
||||
public static T RandomList<T>(IList<T> list) => list[Random(list.Count)];
|
||||
|
||||
public static bool RandomBool() => RandomImpl.NextBool();
|
||||
public static bool RandomBool() => RandomProviders.Provider.NextBool();
|
||||
|
||||
public static int RandomMinMax(int min, int max)
|
||||
{
|
||||
|
|
@ -911,27 +911,27 @@ namespace Server
|
|||
return min;
|
||||
}
|
||||
|
||||
return min + (int)RandomImpl.Next((uint)(max - min + 1));
|
||||
return min + (int)RandomProviders.Provider.Next((uint)(max - min + 1));
|
||||
}
|
||||
|
||||
public static int Random(int from, int count)
|
||||
{
|
||||
if (count == 0) return from;
|
||||
|
||||
if (count > 0) return (int)(from + RandomImpl.Next((uint)count));
|
||||
if (count > 0) return (int)(from + RandomProviders.Provider.Next((uint)count));
|
||||
|
||||
return (int)(from - RandomImpl.Next((uint)-count));
|
||||
return (int)(from - RandomProviders.Provider.Next((uint)-count));
|
||||
}
|
||||
|
||||
public static int Random(int count) => (int)RandomImpl.Next((uint)count);
|
||||
public static int Random(int count) => (int)RandomProviders.Provider.Next((uint)count);
|
||||
|
||||
public static uint Random(uint count) => RandomImpl.Next(count);
|
||||
public static uint Random(uint count) => RandomProviders.Provider.Next(count);
|
||||
|
||||
public static void RandomBytes(byte[] buffer) => RandomImpl.GetBytes(buffer);
|
||||
public static void RandomBytes(byte[] buffer) => RandomProviders.Provider.GetBytes(buffer);
|
||||
|
||||
public static void RandomBytes(Span<byte> buffer) => RandomImpl.GetBytes(buffer);
|
||||
public static void RandomBytes(Span<byte> buffer) => RandomProviders.Provider.GetBytes(buffer);
|
||||
|
||||
public static double RandomDouble() => RandomImpl.NextDouble();
|
||||
public static double RandomDouble() => RandomProviders.Provider.NextDouble();
|
||||
|
||||
/// <summary>
|
||||
/// Random pink, blue, green, orange, red or yellow hue
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: Xoshiro256PlusPlus.cs *
|
||||
* Created: 2019/12/29 - Updated: 2019/12/30 *
|
||||
* Created: 2019/12/29 - Updated: 2020/05/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 *
|
||||
|
|
@ -22,10 +22,11 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class Xoshiro256PlusPlus : RandomNumberGenerator
|
||||
public class Xoshiro256PlusPlus : RandomNumberGenerator, IRandomProvider
|
||||
{
|
||||
private ulong _s0, _s1, _s2, _s3;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ Windows
|
|||
|
||||
#### Requirements
|
||||
- [.NET Core 3.1 Runtime](https://dotnet.microsoft.com/download/dotnet-core/3.1)
|
||||
- Optional: compile and install [Intel DRNG](https://github.com/modernuo/libdrng)
|
||||
|
||||
#### Windows
|
||||
- Run `ModernUO.exe` or `dotnet ModernUO.dll`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue