From 4e8872b94d93afb5834ee4dfcbeb3dfd3b75c92d Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 28 Mar 2017 15:16:31 -0700 Subject: [PATCH] Implement IServer.StopAsync --- .../HttpSysOptions.cs | 6 ---- .../MessagePump.cs | 35 ++++++++++++------- .../MessagePumpTests.cs | 13 +++---- .../RequestTests.cs | 3 +- .../ServerTests.cs | 32 ++++++++++++++--- .../Utilities.cs | 5 +-- 6 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.AspNetCore.Server.HttpSys/HttpSysOptions.cs b/src/Microsoft.AspNetCore.Server.HttpSys/HttpSysOptions.cs index 8b00e14e6a..85c8dcc2e1 100644 --- a/src/Microsoft.AspNetCore.Server.HttpSys/HttpSysOptions.cs +++ b/src/Microsoft.AspNetCore.Server.HttpSys/HttpSysOptions.cs @@ -79,12 +79,6 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } - /// - /// The amount of time to wait for active requests to drain while the server is shutting down. - /// New requests will receive a 503 response in this time period. The default is 5 seconds. - /// - public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5); - internal void SetRequestQueueLimit(RequestQueue requestQueue) { _requestQueue = requestQueue; diff --git a/src/Microsoft.AspNetCore.Server.HttpSys/MessagePump.cs b/src/Microsoft.AspNetCore.Server.HttpSys/MessagePump.cs index 01b8d831e8..e8aa2cca59 100644 --- a/src/Microsoft.AspNetCore.Server.HttpSys/MessagePump.cs +++ b/src/Microsoft.AspNetCore.Server.HttpSys/MessagePump.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys private bool _stopping; private int _outstandingRequests; - private ManualResetEvent _shutdownSignal; + private TaskCompletionSource _shutdownSignal; private readonly ServerAddressesFeature _serverAddresses; @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys _processRequest = new Action(ProcessRequestAsync); _maxAccepts = _options.MaxAccepts; EnableResponseCaching = _options.EnableResponseCaching; - _shutdownSignal = new ManualResetEvent(false); + _shutdownSignal = new TaskCompletionSource(); } internal HttpSysListener Listener { get; } @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys public IFeatureCollection Features { get; } - public void Start(IHttpApplication application) + public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) { if (application == null) { @@ -124,6 +124,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys Listener.Start(); ActivateRequestProcessingLimits(); + + return Task.CompletedTask; } private void ActivateRequestProcessingLimits() @@ -224,7 +226,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys { if (Interlocked.Decrement(ref _outstandingRequests) == 0 && _stopping) { - _shutdownSignal.Set(); + LogHelper.LogInfo(_logger, "All requests drained."); + _shutdownSignal.TrySetResult(0); } } } @@ -242,24 +245,30 @@ namespace Microsoft.AspNetCore.Server.HttpSys context.Dispose(); } - public void Dispose() + public Task StopAsync(CancellationToken cancellationToken) { _stopping = true; // Wait for active requests to drain if (_outstandingRequests > 0) { LogHelper.LogInfo(_logger, "Stopping, waiting for " + _outstandingRequests + " request(s) to drain."); - var drained = _shutdownSignal.WaitOne(Listener.Options.ShutdownTimeout); - if (drained) - { - LogHelper.LogInfo(_logger, "All requests drained successfully."); - } - else + + var waitForStop = new TaskCompletionSource(); + cancellationToken.Register(() => { LogHelper.LogInfo(_logger, "Timed out, terminating " + _outstandingRequests + " request(s)."); - } + waitForStop.TrySetResult(0); + }); + + return Task.WhenAny(_shutdownSignal.Task, waitForStop.Task); } - // All requests are finished + + return Task.CompletedTask; + } + + public void Dispose() + { + _stopping = true; Listener.Dispose(); } diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/MessagePumpTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/MessagePumpTests.cs index 48913a746f..2bcd69e8c9 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/MessagePumpTests.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/MessagePumpTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; +using System.Threading; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging; @@ -25,7 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys serverAddressesFeature.PreferHostingUrls = true; server.Listener.Options.UrlPrefixes.Add(serverAddress); - server.Start(new DummyApplication()); + server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); Assert.Equal(overrideAddress, serverAddressesFeature.Addresses.Single()); } @@ -46,7 +47,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys serverAddressesFeature.Addresses.Add(overrideAddress); server.Listener.Options.UrlPrefixes.Add(serverAddress); - server.Start(new DummyApplication()); + server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); Assert.Equal(serverAddress, serverAddressesFeature.Addresses.Single()); } @@ -63,7 +64,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys serverAddressesFeature.PreferHostingUrls = true; server.Listener.Options.UrlPrefixes.Add(serverAddress); - server.Start(new DummyApplication()); + server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); Assert.Equal(serverAddress, serverAddressesFeature.Addresses.Single()); } @@ -84,7 +85,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys serverAddressesFeature.Addresses.Add(serverAddress); server.Listener.Options.UrlPrefixes.Add(overrideAddress); - server.Start(new DummyApplication()); + server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); Assert.Equal(overrideAddress, serverAddressesFeature.Addresses.Single()); } @@ -100,7 +101,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys var serverAddressesFeature = server.Features.Get(); serverAddressesFeature.Addresses.Add(serverAddress); - server.Start(new DummyApplication()); + server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); } } @@ -109,7 +110,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { using (var server = new MessagePump(Options.Create(new HttpSysOptions()), new LoggerFactory())) { - server.Start(new DummyApplication()); + server.StartAsync(new DummyApplication(), CancellationToken.None).Wait(); Assert.Equal(Constants.DefaultServerAddress, server.Features.Get().Addresses.Single()); } diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestTests.cs index 1e79dff31b..91e4459b78 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/RequestTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; @@ -312,7 +313,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys server.Listener.Options.UrlPrefixes.Add(UrlPrefix.Create(rootUri.Scheme, rootUri.Host, rootUri.Port, path)); } - server.Start(new DummyApplication(app)); + server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); return server; } diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ServerTests.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ServerTests.cs index 10a356b487..029c259b61 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ServerTests.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/ServerTests.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys Task responseTask; ManualResetEvent received = new ManualResetEvent(false); string address; - using (Utilities.CreateHttpServer(out address, httpContext => + using (var server = Utilities.CreateHttpServer(out address, httpContext => { received.Set(); httpContext.Response.ContentLength = 11; @@ -82,11 +82,34 @@ namespace Microsoft.AspNetCore.Server.HttpSys { responseTask = SendRequestAsync(address); Assert.True(received.WaitOne(10000)); + await server.StopAsync(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token); } string response = await responseTask; Assert.Equal("Hello World", response); } + [ConditionalFact] + public async Task Server_DisposeWithoutStopDuringRequest_Aborts() + { + Task responseTask; + var received = new ManualResetEvent(false); + var stopped = new ManualResetEvent(false); + string address; + using (var server = Utilities.CreateHttpServer(out address, httpContext => + { + received.Set(); + Assert.True(stopped.WaitOne(TimeSpan.FromSeconds(10))); + httpContext.Response.ContentLength = 11; + return httpContext.Response.WriteAsync("Hello World"); + })) + { + responseTask = SendRequestAsync(address); + Assert.True(received.WaitOne(TimeSpan.FromSeconds(10))); + } + stopped.Set(); + await Assert.ThrowsAsync(async () => await responseTask); + } + [ConditionalFact] public async Task Server_ShutdownDuringLongRunningRequest_TimesOut() { @@ -95,7 +118,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys bool? shutdown = null; var waitForShutdown = new ManualResetEvent(false); string address; - using (Utilities.CreateHttpServer(out address, httpContext => + using (var server = Utilities.CreateHttpServer(out address, httpContext => { received.Set(); shutdown = waitForShutdown.WaitOne(TimeSpan.FromSeconds(15)); @@ -105,8 +128,9 @@ namespace Microsoft.AspNetCore.Server.HttpSys { responseTask = SendRequestAsync(address); Assert.True(received.WaitOne(TimeSpan.FromSeconds(10))); + Assert.False(shutdown.HasValue); + await server.StopAsync(new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token); } - Assert.False(shutdown.HasValue); waitForShutdown.Set(); await Assert.ThrowsAsync(async () => await responseTask); } @@ -271,7 +295,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys using (server) { - server.Start(new DummyApplication()); + await server.StartAsync(new DummyApplication(), CancellationToken.None); string response = await SendRequestAsync(address); Assert.Equal(string.Empty, response); } diff --git a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs index 3682e1de5e..5744e35d04 100644 --- a/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs +++ b/test/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests/Utilities.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; @@ -55,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys server.Listener.Options.Authentication.AllowAnonymous = allowAnonymous; try { - server.Start(new DummyApplication(app)); + server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); return server; } catch (HttpSysException) @@ -76,7 +77,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { var server = new MessagePump(Options.Create(new HttpSysOptions()), new LoggerFactory()); server.Features.Get().Addresses.Add(UrlPrefix.Create(scheme, host, port, path).ToString()); - server.Start(new DummyApplication(app)); + server.StartAsync(new DummyApplication(app), CancellationToken.None).Wait(); return server; } }