#1208 Default timeout for IHost.StopAsync. Create Host with DI.
This commit is contained in:
parent
cda9ec6fe4
commit
4f3fdaebee
|
|
@ -113,7 +113,7 @@ namespace Microsoft.Extensions.Hosting
|
||||||
BuildAppConfiguration();
|
BuildAppConfiguration();
|
||||||
CreateServiceProvider();
|
CreateServiceProvider();
|
||||||
|
|
||||||
return new Host(_appServices);
|
return _appServices.GetRequiredService<IHost>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildHostConfiguration()
|
private void BuildHostConfiguration()
|
||||||
|
|
@ -179,6 +179,7 @@ namespace Microsoft.Extensions.Hosting
|
||||||
services.AddSingleton(_appConfiguration);
|
services.AddSingleton(_appConfiguration);
|
||||||
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
|
services.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
|
||||||
services.AddSingleton<IHostLifetime, ProcessLifetime>();
|
services.AddSingleton<IHostLifetime, ProcessLifetime>();
|
||||||
|
services.AddSingleton<IHost, Host>();
|
||||||
services.AddOptions();
|
services.AddOptions();
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,22 +8,26 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.Hosting.Internal
|
namespace Microsoft.Extensions.Hosting.Internal
|
||||||
{
|
{
|
||||||
internal class Host : IHost
|
internal class Host : IHost
|
||||||
{
|
{
|
||||||
private ILogger<Host> _logger;
|
private readonly ILogger<Host> _logger;
|
||||||
private IHostLifetime _hostLifetime;
|
private readonly IHostLifetime _hostLifetime;
|
||||||
private ApplicationLifetime _applicationLifetime;
|
private readonly ApplicationLifetime _applicationLifetime;
|
||||||
|
private readonly HostOptions _options;
|
||||||
private IEnumerable<IHostedService> _hostedServices;
|
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));
|
Services = services ?? throw new ArgumentNullException(nameof(services));
|
||||||
_applicationLifetime = Services.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
|
_applicationLifetime = (applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime))) as ApplicationLifetime;
|
||||||
_logger = Services.GetRequiredService<ILogger<Host>>();
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_hostLifetime = Services.GetRequiredService<IHostLifetime>();
|
_hostLifetime = hostLifetime ?? throw new ArgumentNullException(nameof(hostLifetime));
|
||||||
|
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IServiceProvider Services { get; }
|
public IServiceProvider Services { get; }
|
||||||
|
|
@ -57,35 +61,42 @@ namespace Microsoft.Extensions.Hosting.Internal
|
||||||
{
|
{
|
||||||
_logger.Stopping();
|
_logger.Stopping();
|
||||||
|
|
||||||
// Trigger IApplicationLifetime.ApplicationStopping
|
using (var cts = new CancellationTokenSource(_options.ShutdownTimeout))
|
||||||
_applicationLifetime?.StopApplication();
|
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken))
|
||||||
|
|
||||||
IList<Exception> exceptions = new List<Exception>();
|
|
||||||
if (_hostedServices != null) // Started?
|
|
||||||
{
|
{
|
||||||
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);
|
token.ThrowIfCancellationRequested();
|
||||||
}
|
try
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
await hostedService.StopAsync(token).ConfigureAwait(false);
|
||||||
exceptions.Add(ex);
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
exceptions.Add(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await _hostLifetime.StopAsync(cancellationToken);
|
token.ThrowIfCancellationRequested();
|
||||||
|
await _hostLifetime.StopAsync(token);
|
||||||
|
|
||||||
// Fire IApplicationLifetime.Stopped
|
// Fire IApplicationLifetime.Stopped
|
||||||
_applicationLifetime?.NotifyStopped();
|
_applicationLifetime?.NotifyStopped();
|
||||||
|
|
||||||
if (exceptions.Count > 0)
|
if (exceptions.Count > 0)
|
||||||
{
|
{
|
||||||
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
|
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
|
||||||
_logger.StoppedWithException(ex);
|
_logger.StoppedWithException(ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Stopped();
|
_logger.Stopped();
|
||||||
|
|
|
||||||
|
|
@ -500,7 +500,68 @@ namespace Microsoft.Extensions.Hosting
|
||||||
var task = host.StopAsync(cts.Token);
|
var task = host.StopAsync(cts.Token);
|
||||||
cts.Cancel();
|
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))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue