Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit 62f74d5

Browse files
committed
#947 Add IServer.StopAsyc, IWebHost.StopAsync, and make Start async
1 parent 5f9fa5c commit 62f74d5

File tree

18 files changed

+548
-176
lines changed

18 files changed

+548
-176
lines changed

samples/SampleStartups/StartupExternallyControlled.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Threading;
4+
using System.Threading.Tasks;
35
using Microsoft.AspNetCore.Builder;
46
using Microsoft.AspNetCore.Hosting;
57
using Microsoft.AspNetCore.Http;
@@ -34,8 +36,9 @@ public void Start()
3436
.Start(_urls.ToArray());
3537
}
3638

37-
public void Stop()
39+
public async Task StopAsync()
3840
{
41+
await _host.StopAsync(TimeSpan.FromSeconds(5));
3942
_host.Dispose();
4043
}
4144

src/Microsoft.AspNetCore.Hosting.Abstractions/HostingAbstractionsWebHostBuilderExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Linq;
6+
using System.Threading;
7+
using System.Threading.Tasks;
68
using Microsoft.AspNetCore.Hosting.Server;
79
using Microsoft.Extensions.Configuration;
810
using Microsoft.Extensions.DependencyInjection;
@@ -166,7 +168,7 @@ public static IWebHostBuilder PreferHostingUrls(this IWebHostBuilder hostBuilder
166168
public static IWebHost Start(this IWebHostBuilder hostBuilder, params string[] urls)
167169
{
168170
var host = hostBuilder.UseUrls(urls).Build();
169-
host.Start();
171+
host.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
170172
return host;
171173
}
172174
}

src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHost.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57
using Microsoft.AspNetCore.Http.Features;
68

79
namespace Microsoft.AspNetCore.Hosting
@@ -24,6 +26,13 @@ public interface IWebHost : IDisposable
2426
/// <summary>
2527
/// Starts listening on the configured addresses.
2628
/// </summary>
27-
void Start();
29+
Task StartAsync(CancellationToken cancellationToken);
30+
31+
/// <summary>
32+
/// Attempt to gracefully stop the host.
33+
/// </summary>
34+
/// <param name="cancellationToken"></param>
35+
/// <returns></returns>
36+
Task StopAsync(CancellationToken cancellationToken);
2837
}
2938
}

src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ public static class WebHostDefaults
1616
public static readonly string ServerUrlsKey = "urls";
1717
public static readonly string ContentRootKey = "contentRoot";
1818
public static readonly string PreferHostingUrls = "preferHostingUrls";
19+
20+
public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";
1921
}
2022
}

src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.net45.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,24 @@
1616
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder",
1717
"NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureConfiguration(System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Configuration.IConfigurationBuilder> configureDelegate)",
1818
"Kind": "Addition"
19+
},
20+
{
21+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
22+
"OldMemberId": "System.Void Start()",
23+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
24+
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
25+
"Kind": "Modification"
26+
},
27+
{
28+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
29+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
30+
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
31+
"Kind": "Addition"
32+
},
33+
{
34+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
35+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
36+
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
37+
"Kind": "Addition"
1938
}
2039
]

src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.netcore.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,24 @@
1616
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder",
1717
"NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureConfiguration(System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Configuration.IConfigurationBuilder> configureDelegate)",
1818
"Kind": "Addition"
19+
},
20+
{
21+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
22+
"OldMemberId": "System.Void Start()",
23+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
24+
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
25+
"Kind": "Modification"
26+
},
27+
{
28+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
29+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
30+
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
31+
"Kind": "Addition"
32+
},
33+
{
34+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
35+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
36+
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
37+
"Kind": "Addition"
1938
}
2039
]

src/Microsoft.AspNetCore.Hosting.Server.Abstractions/IServer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
57
using Microsoft.AspNetCore.Http.Features;
68

79
namespace Microsoft.AspNetCore.Hosting.Server
@@ -21,6 +23,13 @@ public interface IServer : IDisposable
2123
/// </summary>
2224
/// <param name="application">An instance of <see cref="IHttpApplication{TContext}"/>.</param>
2325
/// <typeparam name="TContext">The context associated with the application.</typeparam>
24-
void Start<TContext>(IHttpApplication<TContext> application);
26+
/// <param name="cancellationToken">Indicates if the server startup should be aborted.</param>
27+
Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken);
28+
29+
/// <summary>
30+
/// Stop processing requests and shut down the server, gracefully if possible.
31+
/// </summary>
32+
/// <param name="cancellationToken">Indicates if the graceful shutdown should be aborted.</param>
33+
Task StopAsync(CancellationToken cancellationToken);
2534
}
2635
}

src/Microsoft.AspNetCore.Hosting.Server.Abstractions/exceptions.net45.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,24 @@
1010
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
1111
"NewMemberId": "System.Void set_PreferHostingUrls(System.Boolean value)",
1212
"Kind": "Addition"
13+
},
14+
{
15+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
16+
"OldMemberId": "System.Void Start<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application)",
17+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
18+
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
19+
"Kind": "Modification"
20+
},
21+
{
22+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
23+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
24+
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
25+
"Kind": "Addition"
26+
},
27+
{
28+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
29+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
30+
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
31+
"Kind": "Addition"
1332
}
1433
]

src/Microsoft.AspNetCore.Hosting.Server.Abstractions/exceptions.netcore.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,24 @@
1010
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
1111
"NewMemberId": "System.Void set_PreferHostingUrls(System.Boolean value)",
1212
"Kind": "Addition"
13+
},
14+
{
15+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
16+
"OldMemberId": "System.Void Start<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application)",
17+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
18+
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
19+
"Kind": "Modification"
20+
},
21+
{
22+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
23+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
24+
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
25+
"Kind": "Addition"
26+
},
27+
{
28+
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
29+
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
30+
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
31+
"Kind": "Addition"
1332
}
1433
]

src/Microsoft.AspNetCore.Hosting/Internal/HostingLoggerExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ public static void Shutdown(this ILogger logger)
8282
}
8383
}
8484

85+
public static void ServerShutdownException(this ILogger logger, Exception ex)
86+
{
87+
if (logger.IsEnabled(LogLevel.Debug))
88+
{
89+
logger.LogDebug(
90+
eventId: LoggerEventIds.ServerShutdownException,
91+
exception: ex,
92+
message: "Server shutdown exception");
93+
}
94+
}
8595

8696
private class HostingLogScope : IReadOnlyList<KeyValuePair<string, object>>
8797
{

src/Microsoft.AspNetCore.Hosting/Internal/LoggerEventIds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ internal static class LoggerEventIds
1616
public const int HostedServiceStartException = 9;
1717
public const int HostedServiceStopException = 10;
1818
public const int HostingStartupAssemblyException = 11;
19+
public const int ServerShutdownException = 12;
1920
}
2021
}

src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.Linq;
88
using System.Reflection;
99
using System.Runtime.InteropServices;
10+
using System.Threading;
11+
using System.Threading.Tasks;
1012
using Microsoft.AspNetCore.Builder;
1113
using Microsoft.AspNetCore.Hosting.Builder;
1214
using Microsoft.AspNetCore.Hosting.Server;
@@ -39,6 +41,8 @@ public class WebHost : IWebHost
3941
private RequestDelegate _application;
4042
private ILogger<WebHost> _logger;
4143

44+
private bool _stopped;
45+
4246
// Used for testing only
4347
internal WebHostOptions Options => _options;
4448

@@ -100,7 +104,7 @@ public void Initialize()
100104
}
101105
}
102106

103-
public virtual void Start()
107+
public virtual async Task StartAsync(CancellationToken cancellationToken)
104108
{
105109
HostingEventSource.Log.HostStart();
106110
_logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
@@ -112,7 +116,8 @@ public virtual void Start()
112116
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
113117
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
114118
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
115-
Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));
119+
var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
120+
await Server.StartAsync(hostingApp, cancellationToken);
116121

117122
// Fire IApplicationLifetime.Started
118123
_applicationLifetime?.NotifyStarted();
@@ -267,23 +272,51 @@ private void EnsureServer()
267272
}
268273
}
269274

270-
public void Dispose()
275+
public async Task StopAsync(CancellationToken cancellationToken)
271276
{
277+
if (_stopped)
278+
{
279+
return;
280+
}
281+
_stopped = true;
282+
272283
_logger?.Shutdown();
273284

285+
if (!cancellationToken.CanBeCanceled)
286+
{
287+
cancellationToken = new CancellationTokenSource(Options.ShutdownTimeout).Token;
288+
}
289+
274290
// Fire IApplicationLifetime.Stopping
275291
_applicationLifetime?.StopApplication();
276292

293+
await Server?.StopAsync(cancellationToken);
294+
277295
// Fire the IHostedService.Stop
278296
_hostedServiceExecutor?.Stop();
279297

280-
(_hostingServiceProvider as IDisposable)?.Dispose();
281-
(_applicationServices as IDisposable)?.Dispose();
282-
283298
// Fire IApplicationLifetime.Stopped
284299
_applicationLifetime?.NotifyStopped();
285300

286301
HostingEventSource.Log.HostStop();
287302
}
303+
304+
public void Dispose()
305+
{
306+
if (!_stopped)
307+
{
308+
try
309+
{
310+
this.StopAsync().GetAwaiter().GetResult();
311+
}
312+
catch (Exception ex)
313+
{
314+
_logger?.ServerShutdownException(ex);
315+
}
316+
}
317+
318+
(_applicationServices as IDisposable)?.Dispose();
319+
(_hostingServiceProvider as IDisposable)?.Dispose();
320+
}
288321
}
289322
}

src/Microsoft.AspNetCore.Hosting/Internal/WebHostOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Globalization;
67
using Microsoft.Extensions.Configuration;
78

89
namespace Microsoft.AspNetCore.Hosting.Internal
@@ -27,6 +28,13 @@ public WebHostOptions(IConfiguration configuration)
2728
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
2829
HostingStartupAssemblies = configuration[WebHostDefaults.HostingStartupAssembliesKey]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
2930
PreferHostingUrls = ParseBool(configuration, WebHostDefaults.PreferHostingUrls);
31+
32+
var timeout = configuration[WebHostDefaults.ShutdownTimeoutKey];
33+
if (!string.IsNullOrEmpty(timeout)
34+
&& int.TryParse(timeout, NumberStyles.None, CultureInfo.InvariantCulture, out var seconds))
35+
{
36+
ShutdownTimeout = TimeSpan.FromSeconds(seconds);
37+
}
3038
}
3139

3240
public string ApplicationName { get; set; }
@@ -47,6 +55,8 @@ public WebHostOptions(IConfiguration configuration)
4755

4856
public bool PreferHostingUrls { get; set; }
4957

58+
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
59+
5060
private static bool ParseBool(IConfiguration configuration, string key)
5161
{
5262
return string.Equals("true", configuration[key], StringComparison.OrdinalIgnoreCase)

0 commit comments

Comments
 (0)