#1208 Default timeout for IHost.StopAsync. Create Host with DI.

This commit is contained in:
Chris Ross (ASP.NET) 2017-10-13 10:42:02 -07:00
parent cda9ec6fe4
commit 4f3fdaebee
4 changed files with 121 additions and 30 deletions

View File

@ -113,7 +113,7 @@ namespace Microsoft.Extensions.Hosting
BuildAppConfiguration();
CreateServiceProvider();
return new Host(_appServices);
return _appServices.GetRequiredService<IHost>();
}
private void BuildHostConfiguration()
@ -179,6 +179,7 @@ namespace Microsoft.Extensions.Hosting
services.AddSingleton(_appConfiguration);
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ProcessLifetime>();
services.AddSingleton<IHost, Host>();
services.AddOptions();
services.AddLogging();

View File

@ -0,0 +1,18 @@
// 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;
namespace Microsoft.Extensions.Hosting
{
/// <summary>
/// Options for <see cref="IHost"/>
/// </summary>
public class HostOptions
{
/// <summary>
/// The default timeout for <see cref="IHost.StopAsync(System.Threading.CancellationToken)"/>.
/// </summary>
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
}
}

View File

@ -8,22 +8,26 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Hosting.Internal
{
internal class Host : IHost
{
private ILogger<Host> _logger;
private IHostLifetime _hostLifetime;
private ApplicationLifetime _applicationLifetime;
private readonly ILogger<Host> _logger;
private readonly IHostLifetime _hostLifetime;
private readonly ApplicationLifetime _applicationLifetime;
private readonly HostOptions _options;
private IEnumerable<IHostedService> _hostedServices;
internal Host(IServiceProvider services)
public Host(IServiceProvider services, IApplicationLifetime applicationLifetime, ILogger<Host> logger,
IHostLifetime hostLifetime, IOptions<HostOptions> options)
{
Services = services ?? throw new ArgumentNullException(nameof(services));
_applicationLifetime = Services.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
_logger = Services.GetRequiredService<ILogger<Host>>();
_hostLifetime = Services.GetRequiredService<IHostLifetime>();
_applicationLifetime = (applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime))) as ApplicationLifetime;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public IServiceProvider Services { get; }
@ -56,36 +60,43 @@ namespace Microsoft.Extensions.Hosting.Internal
public async Task StopAsync(CancellationToken cancellationToken = default)
{
_logger.Stopping();
// Trigger IApplicationLifetime.ApplicationStopping
_applicationLifetime?.StopApplication();
IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null) // Started?
using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
{
foreach (var hostedService in _hostedServices.Reverse())
var token = linkedCts.Token;
// Trigger IApplicationLifetime.ApplicationStopping
_applicationLifetime?.StopApplication();
IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null) // Started?
{
try
foreach (var hostedService in _hostedServices.Reverse())
{
await hostedService.StopAsync(cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
token.ThrowIfCancellationRequested();
try
{
await hostedService.StopAsync(token).ConfigureAwait(false);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
}
await _hostLifetime.StopAsync(cancellationToken);
token.ThrowIfCancellationRequested();
await _hostLifetime.StopAsync(token);
// Fire IApplicationLifetime.Stopped
_applicationLifetime?.NotifyStopped();
// Fire IApplicationLifetime.Stopped
_applicationLifetime?.NotifyStopped();
if (exceptions.Count > 0)
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.StoppedWithException(ex);
throw ex;
if (exceptions.Count > 0)
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.StoppedWithException(ex);
throw ex;
}
}
_logger.Stopped();

View File

@ -500,7 +500,68 @@ namespace Microsoft.Extensions.Hosting
var task = host.StopAsync(cts.Token);
cts.Cancel();
Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(8))));
Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(5))));
}
}
[Fact]
public async Task HostStopAsyncUsesDefaultTimeoutIfGivenTokenDoesNotFire()
{
var service = new Mock<IHostedService>();
service.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
.Returns<CancellationToken>(token =>
{
return Task.Run(() =>
{
token.WaitHandle.WaitOne();
});
});
using (var host = CreateBuilder()
.ConfigureServices((services) =>
{
services.Configure<HostOptions>(options => options.ShutdownTimeout = TimeSpan.FromSeconds(0.5));
services.AddSingleton(service.Object);
})
.Build())
{
await host.StartAsync();
var cts = new CancellationTokenSource();
// Purposefully don't trigger cts
var task = host.StopAsync(cts.Token);
Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(10))));
}
}
[Fact]
public async Task WebHostStopAsyncUsesDefaultTimeoutIfNoTokenProvided()
{
var service = new Mock<IHostedService>();
service.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
.Returns<CancellationToken>(token =>
{
return Task.Run(() =>
{
token.WaitHandle.WaitOne();
});
});
using (var host = CreateBuilder()
.ConfigureServices((services) =>
{
services.Configure<HostOptions>(options => options.ShutdownTimeout = TimeSpan.FromSeconds(0.5));
services.AddSingleton(service.Object);
})
.Build())
{
await host.StartAsync();
var task = host.StopAsync();
Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(10))));
}
}