141 lines
5.3 KiB
C#
141 lines
5.3 KiB
C#
// 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.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Protocols;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv
|
|
{
|
|
public class LibuvTransport : ITransport
|
|
{
|
|
private readonly IEndPointInformation _endPointInformation;
|
|
|
|
private readonly List<IAsyncDisposable> _listeners = new List<IAsyncDisposable>();
|
|
|
|
public LibuvTransport(LibuvTransportContext context, IEndPointInformation endPointInformation)
|
|
: this(new LibuvFunctions(), context, endPointInformation)
|
|
{ }
|
|
|
|
// For testing
|
|
public LibuvTransport(LibuvFunctions uv, LibuvTransportContext context, IEndPointInformation endPointInformation)
|
|
{
|
|
Libuv = uv;
|
|
TransportContext = context;
|
|
|
|
_endPointInformation = endPointInformation;
|
|
}
|
|
|
|
public LibuvFunctions Libuv { get; }
|
|
public LibuvTransportContext TransportContext { get; }
|
|
public List<LibuvThread> Threads { get; } = new List<LibuvThread>();
|
|
|
|
public IApplicationLifetime AppLifetime => TransportContext.AppLifetime;
|
|
public ILibuvTrace Log => TransportContext.Log;
|
|
public LibuvTransportOptions TransportOptions => TransportContext.Options;
|
|
|
|
public async Task StopAsync()
|
|
{
|
|
try
|
|
{
|
|
await Task.WhenAll(Threads.Select(thread => thread.StopAsync(TimeSpan.FromSeconds(5))).ToArray())
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch (AggregateException aggEx)
|
|
{
|
|
// An uncaught exception was likely thrown from the libuv event loop.
|
|
// The original error that crashed one loop may have caused secondary errors in others.
|
|
// Make sure that the stack trace of the original error is logged.
|
|
foreach (var ex in aggEx.InnerExceptions)
|
|
{
|
|
Log.LogCritical("Failed to gracefully close Kestrel.", ex);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
|
|
Threads.Clear();
|
|
#if DEBUG
|
|
GC.Collect();
|
|
GC.WaitForPendingFinalizers();
|
|
GC.Collect();
|
|
#endif
|
|
}
|
|
|
|
public async Task BindAsync()
|
|
{
|
|
// TODO: Move thread management to LibuvTransportFactory
|
|
// TODO: Split endpoint management from thread management
|
|
for (var index = 0; index < TransportOptions.ThreadCount; index++)
|
|
{
|
|
Threads.Add(new LibuvThread(this));
|
|
}
|
|
|
|
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(_endPointInformation, Threads[0]).ConfigureAwait(false);
|
|
}
|
|
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, _endPointInformation, Threads[0]).ConfigureAwait(false);
|
|
|
|
foreach (var thread in Threads.Skip(1))
|
|
{
|
|
var listenerSecondary = new ListenerSecondary(TransportContext);
|
|
_listeners.Add(listenerSecondary);
|
|
await listenerSecondary.StartAsync(pipeName, pipeMessage, _endPointInformation, thread).ConfigureAwait(false);
|
|
}
|
|
}
|
|
}
|
|
catch (UvException ex) when (ex.StatusCode == LibuvConstants.EADDRINUSE)
|
|
{
|
|
await UnbindAsync().ConfigureAwait(false);
|
|
throw new AddressInUseException(ex.Message, ex);
|
|
}
|
|
catch
|
|
{
|
|
await UnbindAsync().ConfigureAwait(false);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task UnbindAsync()
|
|
{
|
|
var disposeTasks = _listeners.Select(listener => listener.DisposeAsync()).ToArray();
|
|
|
|
if (!await WaitAsync(Task.WhenAll(disposeTasks), TimeSpan.FromSeconds(5)).ConfigureAwait(false))
|
|
{
|
|
Log.LogError(0, null, "Disposing listeners failed");
|
|
}
|
|
|
|
_listeners.Clear();
|
|
}
|
|
|
|
private static async Task<bool> WaitAsync(Task task, TimeSpan timeout)
|
|
{
|
|
return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task;
|
|
}
|
|
}
|
|
}
|