Make IServer Start and Stop async
This commit is contained in:
parent
11ab602b2f
commit
e043fa871e
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
|
|
@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
private readonly ITransportFactory _transportFactory;
|
||||
|
||||
private bool _isRunning;
|
||||
private int _stopped;
|
||||
private Heartbeat _heartbeat;
|
||||
|
||||
public KestrelServer(
|
||||
|
|
@ -67,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
|
||||
private InternalKestrelServerOptions InternalOptions { get; }
|
||||
|
||||
public void Start<TContext>(IHttpApplication<TContext> application)
|
||||
public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -129,7 +131,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
_logger.LogDebug($"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");
|
||||
|
||||
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
|
||||
StartLocalhost(ServerAddress.FromUrl(Constants.DefaultServerAddress), serviceContext, application);
|
||||
await StartLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), serviceContext, application, cancellationToken);
|
||||
|
||||
// If StartLocalhost doesn't throw, there is at least one listener.
|
||||
// The port cannot change for "localhost".
|
||||
|
|
@ -174,7 +176,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint.
|
||||
StartLocalhost(parsedAddress, serviceContext, application);
|
||||
await StartLocalhostAsync(parsedAddress, serviceContext, application, cancellationToken);
|
||||
|
||||
// If StartLocalhost doesn't throw, there is at least one listener.
|
||||
// The port cannot change for "localhost".
|
||||
|
|
@ -200,9 +202,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
|
||||
try
|
||||
{
|
||||
transport.BindAsync().Wait();
|
||||
await transport.BindAsync();
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is AddressInUseException)
|
||||
catch (AddressInUseException ex)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {endPoint}: address already in use.", ex);
|
||||
}
|
||||
|
|
@ -219,8 +221,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
// Graceful shutdown if possible
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (Interlocked.Exchange(ref _stopped, 1) == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_transports != null)
|
||||
{
|
||||
var tasks = new Task[_transports.Count];
|
||||
|
|
@ -228,19 +236,27 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
{
|
||||
tasks[i] = _transports[i].UnbindAsync();
|
||||
}
|
||||
Task.WaitAll(tasks);
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
// TODO: Do transport-agnostic connection management/shutdown.
|
||||
for (int i = 0; i < _transports.Count; i++)
|
||||
{
|
||||
tasks[i] = _transports[i].StopAsync();
|
||||
}
|
||||
Task.WaitAll(tasks);
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
_heartbeat?.Dispose();
|
||||
}
|
||||
|
||||
// Ungraceful shutdown
|
||||
public void Dispose()
|
||||
{
|
||||
var cancelledTokenSource = new CancellationTokenSource();
|
||||
cancelledTokenSource.Cancel();
|
||||
StopAsync(cancelledTokenSource.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void ValidateOptions()
|
||||
{
|
||||
if (Options.Limits.MaxRequestBufferSize.HasValue &&
|
||||
|
|
@ -258,7 +274,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
}
|
||||
}
|
||||
|
||||
private void StartLocalhost<TContext>(ServerAddress parsedAddress, ServiceContext serviceContext, IHttpApplication<TContext> application)
|
||||
private async Task StartLocalhostAsync<TContext>(ServerAddress parsedAddress, ServiceContext serviceContext, IHttpApplication<TContext> application, CancellationToken cancellationToken)
|
||||
{
|
||||
if (parsedAddress.Port == 0)
|
||||
{
|
||||
|
|
@ -277,16 +293,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
var connectionHandler = new ConnectionHandler<TContext>(ipv4ListenOptions, serviceContext, application);
|
||||
var transport = _transportFactory.Create(ipv4ListenOptions, connectionHandler);
|
||||
_transports.Add(transport);
|
||||
transport.BindAsync().Wait();
|
||||
await transport.BindAsync();
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is AddressInUseException)
|
||||
catch (AddressInUseException ex)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv4 loopback interface: port already in use.", ex);
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv4 loopback interface: ({ex.Message})");
|
||||
exceptions.Add(ex.InnerException);
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -299,16 +315,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
var connectionHandler = new ConnectionHandler<TContext>(ipv6ListenOptions, serviceContext, application);
|
||||
var transport = _transportFactory.Create(ipv6ListenOptions, connectionHandler);
|
||||
_transports.Add(transport);
|
||||
transport.BindAsync().Wait();
|
||||
await transport.BindAsync();
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is AddressInUseException)
|
||||
catch (AddressInUseException ex)
|
||||
{
|
||||
throw new IOException($"Failed to bind to address {parsedAddress} on the IPv6 loopback interface: port already in use.", ex);
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(0, $"Unable to bind to {parsedAddress} on the IPv6 loopback interface: ({ex.Message})");
|
||||
exceptions.Add(ex.InnerException);
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
|
||||
if (exceptions.Count == 2)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
|
|
@ -161,7 +162,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
private static void StartDummyApplication(IServer server)
|
||||
{
|
||||
server.Start(new DummyApplication(context => TaskCache.CompletedTask));
|
||||
server.StartAsync(new DummyApplication(context => TaskCache.CompletedTask), CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private class MockTransportFactory : ITransportFactory
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv;
|
||||
|
|
@ -27,7 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.TestHelpers
|
|||
|
||||
try
|
||||
{
|
||||
_server.Start(new DummyApplication(app));
|
||||
_server.StartAsync(new DummyApplication(app), CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue