Repository navigation
PosixSignalRegistration.Create does not handle CTRL_CLOSE_EVENT on Windows console close in .NET 10 #119257
Description
Description
When using .NET 10, the breaking change
states that AppDomain.ProcessExit will no longer be raised for CTRL_CLOSE_EVENT. The recommended workaround is to use PosixSignalRegistration.Create.
However, when closing the console window on Windows, the registered handler is not invoked — so cleanup code never executes.
This is a regression from .NET 8, where AppDomain.CurrentDomain.ProcessExit fired consistently on console close and cleanup logic ran.
Reproduction Steps
Minimal repro
Class library (cleanup manager)
using System;
using System.IO;
using System.Runtime.InteropServices;
public static class CleanupManager
{
static CleanupManager()
{
// .NET 10 path (expected to work but does not on Windows console close)
PosixSignalRegistration.Create(PosixSignal.SIGTERM, context =>
{
CleanUp("SIGTERM handler");
});
// Uncomment for .NET 8 path:
// AppDomain.CurrentDomain.ProcessExit += (_, __) => CleanUp("ProcessExit");
}
public static int counter = 0;
private static void CleanUp(string source)
{
var path = Path.Combine(Path.GetTempPath(), "cleanup_log.txt");
File.AppendAllText(path, $"{DateTime.Now}: Cleanup triggered from {source}{Environment.NewLine}");
}
}Console app
class Program
{
static void Main(string[] args)
{
var _ = CleanupManager.counter; // trigger static constructor
Console.WriteLine("App running. Close console window to test.");
while (true)
{
Thread.Sleep(1000);
}
}
}Steps to reproduce
- Create the class library and console app above.
- Run the console app targeting .NET 8.0.
- Close the console window (click the X).
- Observe that cleanup-log.txt is created in %TEMP% with log entries.
- Run the same console app targeting .NET 10.0.
- Close the console window (click the X).
- Observe that no log file is created → handler never fired.
Expected behavior
Since the breaking change doc
instructs developers to use PosixSignalRegistration.Create for CTRL_CLOSE_EVENT, the handler should fire when the console window is closed on Windows.
Or, documentation should explicitly state that CTRL_CLOSE_EVENT is not supported and provide guidance for how cleanup should be handled on Windows.
Actual behavior
- In .NET 10, closing the console window via the X button does not invoke the
PosixSignalRegistration.Create(PosixSignal.SIGTERM, …)handler. - Cleanup logic (temp log file) is never executed.
Regression?
Yes, this works when the console app targets .NET 8.
Known Workarounds
No response
Configuration
- .NET SDK: 8.0.x → works (ProcessExit fires)
- .NET SDK: 10.0.x → fails (SIGTERM handler not invoked)
- OS: Windows 11
- Architecture: x64
Other information
Question / Assistance needed
-
Is this the intended behavior on Windows (that
PosixSignalRegistration.Create(PosixSignal.SIGTERM, …)will not reliably fire forCTRL_CLOSE_EVENT)? -
If so, what is the supported approach for ensuring cleanup when the console window is closed?
-
Is there a new recommended API?
Activity
dotnet-policy-service commentedon Sep 2, 2025
Tagging subscribers to this area: @dotnet/area-system-console
See info in area-owners.md if you want to be subscribed.
KalleOlaviNiemitalo commentedon Sep 2, 2025
Have you tried handling
PosixSignal.SIGHUP? From here, it appears to correspond toCTRL_CLOSE_EVENT:runtime/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs
Line 20 in 46be99d
KalleOlaviNiemitalo commentedon Sep 2, 2025
Related pull requests:
CTRL_CLOSE_EVENTandSIGHUPCTRL_CLOSE_EVENTandCTRL_SHUTDOWN_EVENTClarify default signal handling changes in .NET 10
jkotas commentedon Sep 2, 2025
Thank you for reporting this. I am updating the breaking change docs in dotnet/docs#48219 .
Could you please confirm that subscribing to SIGHUP fixes the behavior for you?
indrajitjadeja commentedon Sep 4, 2025
Thanks @KalleOlaviNiemitalo for pointing out the right enum.
and @jkotas for the document update.
Yes, subscribing to SIGHUP fixes the behavior for me when the console window is closed on Windows.
From my testing and analysis:
AppDomain.ProcessExitis invoked in two scenarios:CTRL_CLOSE_EVENT).Environment.Exit()or whenMainreturns.In .NET 10, because of the breaking change,
ProcessExitis no longer raised forCTRL_CLOSE_EVENT. Instead, I can subscribe toSIGHUPviaPosixSignalRegistration.Create, and the handler is invoked correctly when the console window is closed.However,
ProcessExitis still needed for process termination initiated byEnvironment.Exit()or natural process completion.Additionally, in my experiments,
ProcessExitwas not raised for termination initiated via Task Manager “End Task”, or forCTRL_C_EVENT(Ctrl+C) andCTRL_BREAK_EVENT(Ctrl+Break). These cases behave differently and seem to bypass theProcessExitpipeline.This effectively means that on .NET 10 we now need both:
SIGHUP(to handle console close)ProcessExit(to handle application exit viaEnvironment.Exit()or natural termination)If I keep both registrations in place for cross-version compatibility, then on .NET 8 both are triggered and the handler runs twice. I can work around this, but it raises a question:
==> Besides
SIGHUP(console close) andProcessExit(process termination), are there any other signals I should explicitly subscribe to in .NET 10 to cover cases that ProcessExit previously handled automatically in .NET 8?jkotas commentedon Sep 4, 2025
You should subscribe to SIGTERM as well if you would like to match the earlier behavior on Windows.
indrajitjadeja commentedon Sep 4, 2025
Thanks for the suggestion. Based on my analysis in .NET 10, SIGTERM (which maps to
CTRL_SHUTDOWN_EVENT) is not raised for Windows Service stop events, though it is raised during system shutdown. In contrast,AppDomain.ProcessExitis triggered both for Windows Service stop and system shutdown events. Therefore, when ProcessExit is used in .NET 10, subscribing to SIGTERM may not be required, sinceProcessExitalready covers all relevant termination scenarios for Windows Services and system shutdown.jkotas commentedon Sep 4, 2025
I was commenting on how to replicate the earlier behavior exactly. You are right that it is unnecessary or even undesirable to subscribe to some of these signals depending on the app model used by your process. It was the motivation for why we made this breaking change.
If your process is a Windows Service, you may want to avoid subscribing to any of these signals and use the events that control Windows Service lifetime instead.
jkotas commentedon Sep 4, 2025
Docs clarified in dotnet/docs#48219
indrajitjadeja commentedon Sep 5, 2025
@jkotas
Thanks for clarifying. My concern is more from the class library (NuGet) perspective, rather than a single application model. The library needs to be compatible with .NET 10, but its consumer application could be a console app, Windows Service, or a containerized app.
In that context, my question is whether subscribing to SIGTERM is really necessary, given that there is still a dependency on
AppDomain.ProcessExitin several scenarios (e.g., Windows Service stop, natural process termination). Since the library cannot predict the app model in which it will run, the code needs to honorProcessExitanyway. This is why I’m trying to determine if subscribing to SIGTERM adds any real benefit in addition toProcessExit+SIGHUP.jkotas commentedon Sep 5, 2025
It is best for libraries to expose APIs to let the applications invoke the cleanup as appropriate based on the app model.
Trying to guess the right way to do the cleanups without knowing the appmodel does not always work well. It was the motivation for the breaking change. From https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/10.0/sigterm-signal-handler: "The termination signal handlers registered by the .NET runtime by default were both insufficient for some app models (for example, console and containerized applications) and incompatible with other app models (for example, Windows services)."