Make IServer Start and Stop async

This commit is contained in:
Chris R 2017-04-11 10:18:29 -07:00
parent 11ab602b2f
commit e043fa871e
3 changed files with 37 additions and 19 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
{