Skip to content

PosixSignalRegistration.Create does not handle CTRL_CLOSE_EVENT on Windows console close in .NET 10 #119257

Closed

Description

@indrajitjadeja

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

  1. Create the class library and console app above.
  2. 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.
  1. 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 for CTRL_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

  1. dotnet-policy-service commented on Sep 2, 2025

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-console
    See info in area-owners.md if you want to be subscribed.

  2. KalleOlaviNiemitalo commented on Sep 2, 2025

    @KalleOlaviNiemitalo

    Have you tried handling PosixSignal.SIGHUP? From here, it appears to correspond to CTRL_CLOSE_EVENT:

  3. KalleOlaviNiemitalo commented on Sep 2, 2025

    @KalleOlaviNiemitalo

    Related pull requests:

  4. jkotas commented on Sep 2, 2025

    @jkotas
    Member

    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?

  5. indrajitjadeja commented on Sep 4, 2025

    @indrajitjadeja
    Author

    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:

    • In .NET 8, AppDomain.ProcessExit is invoked in two scenarios:
    1. When the console window is closed (CTRL_CLOSE_EVENT).
    2. When the process terminates due to Environment.Exit() or when Main returns.
    • In .NET 10, because of the breaking change, ProcessExit is no longer raised for CTRL_CLOSE_EVENT. Instead, I can subscribe to SIGHUP via PosixSignalRegistration.Create, and the handler is invoked correctly when the console window is closed.

      However, ProcessExit is still needed for process termination initiated by Environment.Exit() or natural process completion.

    • Additionally, in my experiments, ProcessExit was not raised for termination initiated via Task Manager “End Task”, or for CTRL_C_EVENT (Ctrl+C) and CTRL_BREAK_EVENT (Ctrl+Break). These cases behave differently and seem to bypass the ProcessExit pipeline.

    This effectively means that on .NET 10 we now need both:

    • SIGHUP (to handle console close)
    • ProcessExit (to handle application exit via Environment.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) and ProcessExit (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?

  6. jkotas commented on Sep 4, 2025

    @jkotas
    Member

    Besides SIGHUP (console close) and ProcessExit (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?

    You should subscribe to SIGTERM as well if you would like to match the earlier behavior on Windows.

  7. indrajitjadeja commented on Sep 4, 2025

    @indrajitjadeja
    Author

    You should subscribe to SIGTERM as well if you would like to match the earlier behavior on Windows.

    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.ProcessExit is 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, since ProcessExit already covers all relevant termination scenarios for Windows Services and system shutdown.

  8. jkotas commented on Sep 4, 2025

    @jkotas
    Member

    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.

  9. jkotas commented on Sep 4, 2025

    @jkotas
    Member

    Docs clarified in dotnet/docs#48219

  10. indrajitjadeja commented on Sep 5, 2025

    @indrajitjadeja
    Author

    @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.ProcessExit in 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 honor ProcessExit anyway. This is why I’m trying to determine if subscribing to SIGTERM adds any real benefit in addition to ProcessExit + SIGHUP.

  11. jkotas commented on Sep 5, 2025

    @jkotas
    Member

    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)."

  12. locked and limited conversation to collaborators on Oct 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions