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()
{