-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Consume PosixSignal in Hosting's ConsoleLifetime (#56057)
* Add NetCoreAppCurrent target to Microsoft.Extensions.Hosting * Handle SIGTERM in Hosting and handle just like SIGINT (CTRL+C) Don't listen to ProcessExit on net6.0+ in Hosting anymore. This allows for Environment.Exit to not hang the app. Don't clobber ExitCode during ProcessExit now that SIGTERM is handled separately. For non-net6.0 targets, only wait for the shutdown timeout, so the process doesn't hang forever. Fix #55417 Fix #44086 Fix #50397 Fix #42224 Fix #35990 * Remove _shutdownBlock on netcoreappcurrent, as this is no longer waited on * Change Console.CancelKeyPress to use PosixSignalRegistration SIGINT and SIGQUIT * Use a no-op lifetime on mobile platforms * Add docs for shutdown
- Loading branch information
Showing
16 changed files
with
481 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/libraries/Microsoft.Extensions.Hosting/docs/HostShutdown.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Host Shutdown | ||
|
||
A Hosted Service process can be stopped in the following ways: | ||
|
||
1. If someone doesn't call `Run` or `WaitForShutdown` and the app exits normally with "Main" completing | ||
2. A crash occurs | ||
3. The app is forcefully shut down, e.g. SIGKILL (i.e. CTRL+Z) | ||
4. When ConsoleLifetime is used it listens for the following signals and attempts to stop the host gracefully | ||
1. SIGINT (i.e. CTRL+C) | ||
2. SIGQUIT (i.e. CTRL+BREAK on Windows, CTRL+`\` on Unix) | ||
3. SIGTERM (sent by other apps, e.g. `docker stop`) | ||
5. The app calls `Environment.Exit(code)` | ||
|
||
Scenarios (1), (2), and (3) aren't handled directly by the Hosting code. The owner of the process needs to deal with | ||
them the same as any application. | ||
|
||
Scenarios (4) and (5) are handled specially by the built-in Hosting logic. Specifically by the ConsoleLifetime | ||
class. ConsoleLifetime tries to handle the "shutdown" signals listed in (4) and allow for a graceful exit to the | ||
application. | ||
|
||
Before .NET 6, there wasn't a way for .NET code to gracefully handle SIGTERM. To work around this limitation, | ||
ConsoleLifetime would subscribe to `AppDomain.CurrentDomain.ProcessExit`. When `ProcessExit` was raised, | ||
ConsoleLifetime would signal the Host to stop, and block the `ProcessExit` thread, waiting for the Host to stop. | ||
This would allow for the clean up code in the application to run - for example, `IHostedService.StopAsync` and code after | ||
`Host.Run` in the Main method. | ||
|
||
This caused other issues because SIGTERM wasn't the only way `ProcessExit` was raised. It is also raised by code | ||
in the application calling `Environment.Exit`. `Environment.Exit` isn't a graceful way of shutting down a process. | ||
It raises the `ProcessExit` event and then exits the process. The end of the Main method doesn't get executed. However, | ||
since ConsoleLifetime blocked `ProcessExit` waiting for the Host to shutdown, this behavior also lead to [deadlocks][deadlocks] | ||
due to `Environment.Exit` also blocking waiting for `ProcessExit` to return. Additionally, since SIGTERM handling was attempting | ||
to gracefully shut down the process, ConsoleLifetime would set the ExitCode to `0`, which [clobbered][clobbered] the user's | ||
exit code passed to `Environment.Exit`. | ||
|
||
[deadlocks]: https://github.com/dotnet/runtime/issues/50397 | ||
[clobbered]: https://github.com/dotnet/runtime/issues/42224 | ||
|
||
In .NET 6, we added new support to handle [POSIX signals][POSIX signals]. This allows for ConsoleLifetime to specifically | ||
handle SIGTERM gracefully, and no longer get involved when `Environment.Exit` is invoked. For .NET 6+, ConsoleLifetime no longer | ||
has logic to handle scenario (5) above. Apps that call `Environment.Exit`, and need to do clean up logic, can subscribe to | ||
`ProcessExit` themselves. Hosting will no longer attempt to gracefully stop the Host in this scenario. | ||
|
||
[POSIX signals]: https://github.com/dotnet/runtime/issues/50527 | ||
|
||
### Hosting Shutdown Process | ||
|
||
The following sequence model shows how the signals in (4) above are handled internally in the Hosting code. It isn't necessary | ||
for the majority of users to understand this process. But for developers that need a deep understanding, this may help them | ||
get started. | ||
|
||
After the Host has been started, when a user calls `Run` or `WaitForShutdown`, a handler gets registered for | ||
`ApplicationLifetime.ApplicationStopping`. Execution is paused in `WaitForShutdown`, waiting for the `ApplicationStopping` | ||
event to be raised. This is how the "Main" method doesn't return right away, and the app stays running until | ||
`Run`/`WaitForShutdown` returns. | ||
|
||
When a signal is sent to the process, it kicks off the following sequence. | ||
|
||
![image](images/HostShutdownSequence.png) | ||
|
||
The control flows from `ConsoleLifetime` to `ApplicationLifetime` to raise the `ApplicationStopping` event. This signals | ||
`WaitForShutdownAsync` to unblock the "Main" execution code. In the meantime, the POSIX signal handler returns with | ||
`Cancel = true` since this POSIX signal has been handled. | ||
|
||
The "Main" execution code starts executing again and tells the Host to `StopAsync()`, which in turn stops all the Hosted | ||
Services and raises any other stopped events. | ||
|
||
Finally, `WaitForShutdown` exits, allowing for any application clean up code to execute, and for the "Main" method | ||
to exit gracefully. |
Binary file added
BIN
+56.7 KB
src/libraries/Microsoft.Extensions.Hosting/docs/images/HostShutdownSequence.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.netcoreapp.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting.Internal; | ||
|
||
namespace Microsoft.Extensions.Hosting | ||
{ | ||
public partial class HostBuilder | ||
{ | ||
private static void AddLifetime(ServiceCollection services) | ||
{ | ||
if (!OperatingSystem.IsAndroid() && !OperatingSystem.IsBrowser() && !OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst() && !OperatingSystem.IsTvOS()) | ||
{ | ||
services.AddSingleton<IHostLifetime, ConsoleLifetime>(); | ||
} | ||
else | ||
{ | ||
services.AddSingleton<IHostLifetime, NullLifetime>(); | ||
} | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/libraries/Microsoft.Extensions.Hosting/src/HostBuilder.notnetcoreapp.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting.Internal; | ||
|
||
namespace Microsoft.Extensions.Hosting | ||
{ | ||
public partial class HostBuilder | ||
{ | ||
private static void AddLifetime(ServiceCollection services) | ||
{ | ||
services.AddSingleton<IHostLifetime, ConsoleLifetime>(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.