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