// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting.Internal; namespace Microsoft.AspNetCore.Hosting { public static class WebHostExtensions { /// /// Attempts to gracefully stop the host with the given timeout. /// /// /// The timeout for stopping gracefully. Once expired the /// server may terminate any remaining active connections. /// public static Task StopAsync(this IWebHost host, TimeSpan timeout) { return host.StopAsync(new CancellationTokenSource(timeout).Token); } /// /// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM. /// /// The running . public static void WaitForShutdown(this IWebHost host) { host.WaitForShutdownAsync().GetAwaiter().GetResult(); } /// /// Returns a Task that completes when shutdown is triggered via the given token, Ctrl+C or SIGTERM. /// /// The running . /// The token to trigger shutdown. public static async Task WaitForShutdownAsync(this IWebHost host, CancellationToken token = default) { var done = new ManualResetEventSlim(false); using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token)) { AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: string.Empty); try { await host.WaitForTokenShutdownAsync(cts.Token); } finally { done.Set(); } } } /// /// Runs a web application and block the calling thread until host shutdown. /// /// The to run. public static void Run(this IWebHost host) { host.RunAsync().GetAwaiter().GetResult(); } /// /// Runs a web application and returns a Task that only completes when the token is triggered or shutdown is triggered. /// /// The to run. /// The token to trigger shutdown. public static async Task RunAsync(this IWebHost host, CancellationToken token = default) { // Wait for token shutdown if it can be canceled if (token.CanBeCanceled) { await host.RunAsync(token, shutdownMessage: null); return; } // If token cannot be canceled, attach Ctrl+C and SIGTERM shutdown var done = new ManualResetEventSlim(false); using (var cts = new CancellationTokenSource()) { var shutdownMessage = host.Services.GetRequiredService().SuppressStatusMessages ? string.Empty : "Application is shutting down..."; AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: shutdownMessage); try { await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down."); } finally { done.Set(); } } } private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage) { using (host) { await host.StartAsync(token); var hostingEnvironment = host.Services.GetService(); var options = host.Services.GetRequiredService(); if (!options.SuppressStatusMessages) { Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}"); Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}"); var serverAddresses = host.ServerFeatures.Get()?.Addresses; if (serverAddresses != null) { foreach (var address in serverAddresses) { Console.WriteLine($"Now listening on: {address}"); } } if (!string.IsNullOrEmpty(shutdownMessage)) { Console.WriteLine(shutdownMessage); } } await host.WaitForTokenShutdownAsync(token); } } private static void AttachCtrlcSigtermShutdown(CancellationTokenSource cts, ManualResetEventSlim resetEvent, string shutdownMessage) { void Shutdown() { if (!cts.IsCancellationRequested) { if (!string.IsNullOrEmpty(shutdownMessage)) { Console.WriteLine(shutdownMessage); } try { cts.Cancel(); } catch (ObjectDisposedException) { } } // Wait on the given reset event resetEvent.Wait(); }; AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => Shutdown(); Console.CancelKeyPress += (sender, eventArgs) => { Shutdown(); // Don't terminate the process immediately, wait for the Main thread to exit gracefully. eventArgs.Cancel = true; }; } private static async Task WaitForTokenShutdownAsync(this IWebHost host, CancellationToken token) { var applicationLifetime = host.Services.GetService(); token.Register(state => { ((IApplicationLifetime)state).StopApplication(); }, applicationLifetime); var waitForStop = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); applicationLifetime.ApplicationStopping.Register(obj => { var tcs = (TaskCompletionSource)obj; tcs.TrySetResult(null); }, waitForStop); await waitForStop.Task; // WebHost will use its default ShutdownTimeout if none is specified. await host.StopAsync(); } } }