-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid Blocking Finalizer Thread During Shutdown in SystemEvents #108489
Conversation
cc: @ericstj , @JeremyKuhne , @stephentoub |
src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
Outdated
Show resolved
Hide resolved
{ | ||
public abstract class ShutdownTest : SystemEventsTest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why this was originally marked as abstract
but it seems that was causing the tests not to run for this class
RemoteExecutor.Invoke(() => | ||
{ | ||
// Block the SystemEvents thread. Regression test for https://github.com/dotnet/winforms/issues/11944 | ||
SystemEvents.UserPreferenceChanged += (o, e) => { while (true) { } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an artificial repro to make sure shutdown can happen despite a hang occuring. We will include a regression test of the issue scenario in the winforms repo.
public static bool EnableLegacySystemEventsShutdownThreadJoin | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
get => GetCachedSwitchValue("Switch.SystemEvents.EnableLegacySystemEventsShutdownThreadJoin", ref s_enableLegacySystemEventsShutdownThreadJoin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you expect that this fix is likely going to break applications out there?
If we are going to introduce a config switch for this, are we also going to file a breaking change notice for this that explains when people should use this config switch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I think it is unlikely, I suppose that there could be an app out there that absolutely needs to know when the SystemEvents thread has shutdown (though given how the code was in Framework, there wouldn't be a good way to know that for STA threads so this makes chances even slimmer). I'm not sure what scenario that would be and it would be nice to know about the specifics of the scenario if such cases exist. We could introduce the switch later if we get a report. That way we'd also have better understanding of the scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think introducing config switches reactively only once there is actual compat problem is a better strategy. It keeps the code simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK without the compat switch for 10.0 - but if we backport to servicing I think we might need it.
We will have a breaking change with this - we discussed that we can no longer guarantee that EventsThreadShutdown
will be raised.
src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
Outdated
Show resolved
Hide resolved
…obsolete EventsThreadShutdown
LGTM. @dotnet/area-microsoft-win32 Could you please take a look and merge if this change looks fine? |
Co-authored-by: Jan Kotas <[email protected]>
src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/src/CompatibilitySuppressions.xml
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/ref/Microsoft.Win32.SystemEvents.csproj
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/src/CompatibilitySuppressions.xml
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/ref/Microsoft.Win32.SystemEvents.cs
Outdated
Show resolved
Hide resolved
High quality PR description BTW 🙂 |
src/libraries/Microsoft.Win32.SystemEvents/ref/Microsoft.Win32.SystemEvents.cs
Outdated
Show resolved
Hide resolved
src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj
Show resolved
Hide resolved
Just double checking, should this API get obsoleted on all TFMs or just on net8.0, net9.0 or net10.0? |
In this change I've removed shutdown handling, and my understanding is that the change will affect all TFMs is that correct? If so then I think this needs to be obsolete for all. |
src/libraries/Microsoft.Win32.SystemEvents/ref/Microsoft.Win32.SystemEvents.cs
Outdated
Show resolved
Hide resolved
// is STA, as there are no guarantees this thread will pump nor still be alive | ||
// for the desired duration. | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these just stylistic changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is just a style change. I inverted a couple if statement in this method and changed ==
/!=
to use is
/is not
src/libraries/Microsoft.Win32.SystemEvents/ref/Microsoft.Win32.SystemEvents.cs
Outdated
Show resolved
Hide resolved
@lonitra sorry for the delay in responding. So the suppression file update is fine to take, I just double checked (and pushed a commit to your branch). It means that the current package uses a different ObsoleteAttribute type for netstandard2.0 than the previous 9.0.0-P7 package. This is expected and is fine. |
src/libraries/Microsoft.Win32.SystemEvents/ref/Microsoft.Win32.SystemEvents.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Stephen Toub <[email protected]>
Breaking change issue: dotnet/docs#43563 |
Fixes dotnet/winforms#11944
The SystemEvents thread can block the Finalizer thread indefinitely even though it is categorized as a background thread. When the main thread kicks off shutdown, the Finalizer thread will raise a
ProcessExit
event, which will callback toSystemEvents.Shutdown
. This occurs beforeAppDomain.IsFinalizingForUnload
orEnvironment.HasShutdownStarted
is set totrue
. Due to this, any code that needs a response from the main thread cannot know main thread will not respond as it is in the middle of shutdown.SystemEvents.Shutdown
has aThread.Join
call which waits for the SystemEvents thread to come back before completing the method, meaning that the Finalizer thread is blocked until SystemEvents thread finishes. This can cause synchronous callbacks to deadlock. An example of this is seen in dotnet/winforms#11944. The following is a callstack of the deadlock from issue reproMain thread
Finalizer thread:
SystemEvents thread:
SystemEvents thread had deferred to
WindowsFormSynchronizationContext
to fire events, which waits for tasks to complete on the main thread (this occurred prior to shut down). It cannot see that main thread is now trying to shut down, but the main thread stuck waiting for the Finalizer thread to finish, which is waiting for SystemEvents thread to finish.This deadlock was made easier to run into in the common scenario via #53467. Prior to that change and in Framework, this type of deadlock was more difficult to hit because we did not spawn a message loop thread for SystemEvents for STA threads. This meant that SystemEvent messages were taken care of using the same message pump as the main thread, so there is a slimmer chance that a deadlock could occur in this way. We don't want to revert the change above because it has fixed a different type of deadlock and made behavior more consistent.
The change here avoids calling
Thread.Join
during shutdown to avoid blocking the Finalizer thread. Consequentially, because the Finalizer thread no longer waits for SystemEvents thread to finish before shutting down,SystemEvents.EventsThreadShutdown
is no longer guaranteed to be called before the process exits so marking this as obsolete with recommendation to hook toAppDomain.ProcessExit
instead. Note that in FrameworkSystemEvents.EventsThreadShutdown
is never called for STA threads ass_windowThread
is never created for STA threads and that event is only raised in https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs#L1276 which only gets called ifs_windowThread
is created.