#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();
|
||||
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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 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();
|
||||
|
|
|
|||
|
|
@ -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))));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue