diff --git a/build/dependencies.props b/build/dependencies.props index 419f253f2c..8650a6c2af 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -8,6 +8,7 @@ 15.3.0-* 1.0.1 2.3.0-beta2-* + 4.7.1 1.4.0 3.2.0 diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs b/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs index 68b333bab2..17c81c0948 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs @@ -277,9 +277,14 @@ namespace Microsoft.AspNetCore.Hosting.Internal _logger?.Shutdown(); + var timeoutToken = new CancellationTokenSource(Options.ShutdownTimeout).Token; if (!cancellationToken.CanBeCanceled) { - cancellationToken = new CancellationTokenSource(Options.ShutdownTimeout).Token; + cancellationToken = timeoutToken; + } + else + { + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token; } // Fire IApplicationLifetime.Stopping diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj b/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj index 8916957acd..25757440ad 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj @@ -22,6 +22,7 @@ + diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs index 66f751aac4..8bbd0bf3f4 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs @@ -20,6 +20,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Hosting @@ -187,6 +188,114 @@ namespace Microsoft.AspNetCore.Hosting } } + [Fact] + public async Task WebHostStopAsyncUsesDefaultTimeoutIfGivenTokenDoesNotFire() + { + var data = new Dictionary + { + { WebHostDefaults.ShutdownTimeoutKey, "1" } + }; + + var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build(); + + var server = new Mock(); + server.Setup(s => s.StopAsync(It.IsAny())) + .Returns(Task.CompletedTask) + .Callback(token => + { + token.WaitHandle.WaitOne(); + }); + + using (var host = CreateBuilder(config) + .ConfigureServices(services => + { + services.AddSingleton(server.Object); + }) + .UseStartup("Microsoft.AspNetCore.Hosting.Tests") + .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 data = new Dictionary + { + { WebHostDefaults.ShutdownTimeoutKey, "1" } + }; + + var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build(); + + var server = new Mock(); + server.Setup(s => s.StopAsync(It.IsAny())) + .Returns(Task.CompletedTask) + .Callback(token => + { + token.WaitHandle.WaitOne(); + }); + + using (var host = CreateBuilder(config) + .ConfigureServices(services => + { + services.AddSingleton(server.Object); + }) + .UseStartup("Microsoft.AspNetCore.Hosting.Tests") + .Build()) + { + await host.StartAsync(); + + var task = host.StopAsync(); + + Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(10)))); + } + } + + [Fact] + public async Task WebHostStopAsyncCanBeCancelledEarly() + { + var data = new Dictionary + { + { WebHostDefaults.ShutdownTimeoutKey, "10" } + }; + + var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build(); + + var server = new Mock(); + server.Setup(s => s.StopAsync(It.IsAny())) + .Returns(Task.CompletedTask) + .Callback(token => + { + token.WaitHandle.WaitOne(); + }); + + using (var host = CreateBuilder(config) + .ConfigureServices(services => + { + services.AddSingleton(server.Object); + }) + .UseStartup("Microsoft.AspNetCore.Hosting.Tests") + .Build()) + { + await host.StartAsync(); + + var cts = new CancellationTokenSource(); + + var task = host.StopAsync(cts.Token); + cts.Cancel(); + + Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(8)))); + } + } + [Fact] public void WebHostApplicationLifetimeEventsOrderedCorrectlyDuringShutdown() {