Skip to content

Fix possible deadlocks on PosixSignalRegistration disposal on Windows#124893

Open
mrek-msft wants to merge 23 commits intodotnet:mainfrom
mrek-msft:dev/mrek/meh-sig-hndlr-deadlock
Open

Fix possible deadlocks on PosixSignalRegistration disposal on Windows#124893
mrek-msft wants to merge 23 commits intodotnet:mainfrom
mrek-msft:dev/mrek/meh-sig-hndlr-deadlock

Conversation

@mrek-msft
Copy link
Member

@mrek-msft mrek-msft commented Feb 26, 2026

Implements following proposal from #117753

What do you think about changing PosixSignalRegistration to not try to remove it's handler once it's been registered once?

Sounds good to me on Windows. I am not sure whether it is a good idea on Unix.

PR removes unregistration to prevent AB/BA deadlock (where A = lock (s_registrations), B = critical section in KernelBase.dll) when one thread is unregistering and other thread fires HandlerRoutine. The same can happen when registering, so I moved SetConsoleCtrlHandler registration from s_registrations locked section.

Also removes leaking handlers in Hosting because now we can call Dispose on them directly in Handler.

@dotnet-policy-service
Copy link
Contributor

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

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Windows PosixSignalRegistration to reduce shutdown deadlock risk by ensuring the console control handler is only registered once and by removing handler unregistration.

Changes:

  • Introduces s_isCtrlHandlerRegisteredOnce to prevent repeated SetConsoleCtrlHandler registrations.
  • Moves SetConsoleCtrlHandler(Add: true) outside the s_registrations lock to avoid A→B / B→A deadlock scenarios.
  • Removes SetConsoleCtrlHandler(Add: false) unregistration when the last registration is disposed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs:77

  • The token is added to s_registrations even when the SetConsoleCtrlHandler registration failed or was skipped. If registerCtrlHandler is false (another thread is registering), the current thread will proceed to add its token to s_registrations before the other thread's SetConsoleCtrlHandler call completes. If that call fails, this registration will be in s_registrations but the handler won't be registered with Windows, causing signals to be silently ignored.

Consider adding the token to s_registrations only after successful handler registration, or checking s_isCtrlHandlerRegistering again and verifying the handler was successfully registered.

            lock (s_registrations)
            {
                s_isCtrlHandlerRegistering = false;

                if (!s_registrations.TryGetValue(signo, out List<Token>? tokens))
                {
                    s_registrations[signo] = tokens = new List<Token>();
                }

                tokens.Add(token);
            }

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs:12

  • The variable name s_isCtrlHandlerRegistering uses the present continuous tense (-ing form) suggesting an ongoing action, but it's set to true before registration starts and false after it completes (or fails), making it a state flag. Consider renaming to s_ctrlHandlerRegistrationInProgress or s_isRegisteringCtrlHandler for clarity, or use a simpler name like s_handlerRegistrationInProgress that better conveys it's a guard flag for the registration operation.
        private static bool s_isCtrlHandlerRegistering;

src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/PosixSignalRegistration.Windows.cs:77

  • The concurrent registration scenario where one thread's SetConsoleCtrlHandler call fails while another thread is waiting is not covered by tests. This is a critical scenario because it can lead to registrations being added to s_registrations without the handler actually being registered with Windows, causing signals to be silently ignored. Consider adding a test that simulates concurrent registration with one failing, or at least document this edge case if it's considered too difficult to test reliably.
            lock (s_registrations)
            {
                s_isCtrlHandlerRegistering = false;

                if (!s_registrations.TryGetValue(signo, out List<Token>? tokens))
                {
                    s_registrations[signo] = tokens = new List<Token>();
                }

                tokens.Add(token);
            }

Copilot AI review requested due to automatic review settings March 9, 2026 12:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.


You can also share your feedback on Copilot code review. Take the survey.

…pServices/PosixSignalRegistration.Windows.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 9, 2026 16:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

@mrek-msft
Copy link
Member Author

mrek-msft commented Mar 12, 2026

I pushed also changes to Hosting which was the second issue mentioned in #117753 Potential shutdown deadlocks in Microsoft.Extensions.Hosting on Windows. I originally planed to fix them in separate PRs, but since it is simple and related, I added them here.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +164 to +166
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[MemberData(nameof(SignalTestData))]
public void SignalHandler_CanDisposeInHandler(PosixSignal signal)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses SignalTestData, which on Unix includes signals like SIGCHLD/SIGWINCH (default: ignored) and SIGTSTP/SIGTTIN/SIGTTOU (default: stop, not terminate). The test expects the second signal to terminate the process, so it will fail or hang/return -1 for those signals. Limit this test’s data to signals whose default action is process termination (e.g., SIGINT/SIGQUIT/SIGTERM), or make the expected outcome conditional per-signal.

Copilot uses AI. Check for mistakes.
Comment on lines +207 to +211
// Wait for second signal which should temrinate process by default system handler
Thread.Sleep(1000);

// If we did not terminated yet, return failure exit code
return -1;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remote process exits with return -1 after a fixed delay. This makes the test timing-sensitive: if the parent doesn’t manage to read stdout and send the second signal within that window (CI slowness / scheduling), the child can exit normally with -1 and the test will fail. Instead, block indefinitely (or for a generous timeout) after the first signal and rely on the second signal to terminate the process.

Suggested change
// Wait for second signal which should temrinate process by default system handler
Thread.Sleep(1000);
// If we did not terminated yet, return failure exit code
return -1;
// After the first signal unregisters the handler, block indefinitely and
// rely on the second signal to terminate the process via the default handler.
while (true)
{
Thread.Sleep(Timeout.Infinite);
}

Copilot uses AI. Check for mistakes.
@jkotas
Copy link
Member

jkotas commented Mar 12, 2026

Could you please go over the copilot feedback?

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 13, 2026 10:22
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +164 to +170
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[MemberData(nameof(SignalTestData))]
public void SignalHandler_CanDisposeInHandler(PosixSignal signal)
{
const string PosixSignalRegistrationCreatedMessage = "PosixSignalRegistration created...";
const string PosixSignalHandlerStartedMessage = "PosixSignalHandlerFinishedMessage created...";
const string PosixSignalHandlerDisposedMessage = "PosixSignalHandlerDisposedMessage created...";

private static unsafe PosixSignalRegistration Register(PosixSignal signal, Action<PosixSignalContext> handler)
/// <summary>
/// Lock object used to serialize registration changes that affect calls to SetConsoleCtrlHandler.
Copilot AI review requested due to automatic review settings March 13, 2026 10:50
mrek-msft and others added 4 commits March 13, 2026 11:52
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.


You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +216 to +233
while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalRegistrationCreatedMessage))
{
Thread.Sleep(20);
}

try
{
SendSignal(signal, remoteHandle.Process.Id);

while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalHandlerStartedMessage))
{
Thread.Sleep(20);
}

while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalHandlerDisposedMessage))
{
Thread.Sleep(20);
}
Comment on lines +225 to +228
while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalHandlerStartedMessage))
{
Thread.Sleep(20);
}
Comment on lines +216 to +234
while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalRegistrationCreatedMessage))
{
Thread.Sleep(20);
}

try
{
SendSignal(signal, remoteHandle.Process.Id);

while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalHandlerStartedMessage))
{
Thread.Sleep(20);
}

while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(PosixSignalHandlerDisposedMessage))
{
Thread.Sleep(20);
}

@mrek-msft mrek-msft changed the title Register CtrlHandler in PosixSignalRegistration only once on Windows Fix possible deadlocks on PosixSignalRegistration disposal on Windows Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants