From 8b1fbad10e12e7ddab98ae7a17891eca6e6bcfd9 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 3 May 2018 17:01:36 -0700 Subject: [PATCH] Don't complete KestrelServer.StopAsync task inline (#2534) --- src/Kestrel.Core/KestrelServer.cs | 2 +- test/Kestrel.Core.Tests/KestrelServerTests.cs | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/Kestrel.Core/KestrelServer.cs b/src/Kestrel.Core/KestrelServer.cs index 9e549a2282..d83d29ef84 100644 --- a/src/Kestrel.Core/KestrelServer.cs +++ b/src/Kestrel.Core/KestrelServer.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core private bool _hasStarted; private int _stopping; - private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(); + private readonly TaskCompletionSource _stoppedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public KestrelServer(IOptions options, ITransportFactory transportFactory, ILoggerFactory loggerFactory) : this(transportFactory, CreateServiceContext(options, loggerFactory)) diff --git a/test/Kestrel.Core.Tests/KestrelServerTests.cs b/test/Kestrel.Core.Tests/KestrelServerTests.cs index e8a2ca2a13..16e1ce179d 100644 --- a/test/Kestrel.Core.Tests/KestrelServerTests.cs +++ b/test/Kestrel.Core.Tests/KestrelServerTests.cs @@ -327,6 +327,63 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); } + [Fact] + public async Task StopAsyncDispatchesSubsequentStopAsyncContinuations() + { + var options = new KestrelServerOptions + { + ListenOptions = + { + new ListenOptions(new IPEndPoint(IPAddress.Loopback, 0)) + } + }; + + var unbindTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var mockTransport = new Mock(); + mockTransport + .Setup(transport => transport.BindAsync()) + .Returns(Task.CompletedTask); + mockTransport + .Setup(transport => transport.UnbindAsync()) + .Returns(unbindTcs.Task); + mockTransport + .Setup(transport => transport.StopAsync()) + .Returns(Task.CompletedTask); + + var mockTransportFactory = new Mock(); + mockTransportFactory + .Setup(transportFactory => transportFactory.Create(It.IsAny(), It.IsAny())) + .Returns(mockTransport.Object); + + var mockLoggerFactory = new Mock(); + var mockLogger = new Mock(); + mockLoggerFactory.Setup(m => m.CreateLogger(It.IsAny())).Returns(mockLogger.Object); + var server = new KestrelServer(Options.Create(options), mockTransportFactory.Object, mockLoggerFactory.Object); + await server.StartAsync(new DummyApplication(), default); + + var stopTask1 = server.StopAsync(default); + var stopTask2 = server.StopAsync(default); + + Assert.False(stopTask1.IsCompleted); + Assert.False(stopTask2.IsCompleted); + + var continuationTask = Task.Run(async () => + { + await stopTask2; + stopTask1.Wait(); + }); + + unbindTcs.SetResult(null); + + // If stopTask2 is completed inline by the first call to StopAsync, stopTask1 will never complete. + await stopTask1.TimeoutAfter(TestConstants.DefaultTimeout); + await stopTask2.TimeoutAfter(TestConstants.DefaultTimeout); + await continuationTask.TimeoutAfter(TestConstants.DefaultTimeout); + + mockTransport.Verify(transport => transport.UnbindAsync(), Times.Once); + } + private static KestrelServer CreateServer(KestrelServerOptions options, ILogger testLogger) { return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) }));