aspnetcore/src/Kestrel.Transport.Libuv/LibuvTransport.cs

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