winforms: Hang during exit if `WM_SETTINGCHANGE` is received
.NET version
7.0.9-servicing.23321.6+dd121ee97234c7490f6111cf0c7bfa9235923f81
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
Due to the race condition nature of the issue, it is unclear whether the issue exists at a lower frequency or does not exist on prior versions. I was unable to reproduce the issue in .Net 4.7.2 or net5. The issue occurs reliably with .net 7.
Issue description
When a WM_SETTINGCHANGE
message is received after the main form of an application has started closing, the application hangs. The use case where this happens reliably is when the window is an App Bar, as it will always generate a desktop change when the window closes.
Apparent timeline of events:
- On main thread, in closing the only Form, a change to the desktop layout is triggered (specifically due to removing an AppBar with
SHAppBarMessage(ABM_REMOVE, ...)
) - The main thread begins shutting down (disposing controls, form, etc…)
- The “.NET System Events” thread receives
WM_SETTINGCHANGE
and Invokes to the main thread - Since the main thread has not shutdown, the test at
WindowsFormsSynchronizationContext.Send:82
succeeds - The “.Net System Events” thread enters a
WaitHandle
- The main thread, having had its main form closed, exits the message loop and
Main
returns - Hang
Call stacks at hang:
Main Thread (no .Net stack, only native)
win32u.dll!NtUserMsgWaitForMultipleObjectsEx()
combase.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 2108
combase.dll!ClassicSTAThreadWaitForHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * pdwIndex) Line 54
combase.dll!CoWaitForMultipleHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * lpdwindex) Line 126
[Inline Frame] hostpolicy.dll!coreclr_t::shutdown(int *) Line 152
hostpolicy.dll!run_app_for_context(const hostpolicy_context_t & context, int argc, const wchar_t * * argv) Line 264
...snip...
.NET System Events thread
System.Private.CoreLib.dll!System.Threading.WaitHandle.WaitOneNoCheck(int millisecondsTimeout) Line 139
at /_/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.cs(139)
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle) Line 3967
at /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs(3967)
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) Line 7141
at /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs(7141)
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) Line 6587
at /_/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs(6587)
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) Line 88
at /_/src/System.Windows.Forms/src/System/Windows/Forms/WindowsFormsSynchronizationContext.cs(88)
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization, object[] args) Line 35
at Microsoft.Win32\SystemEvents.cs(35)
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization, object key, object[] args) Line 850
at Microsoft.Win32\SystemEvents.cs(850)
locals:
array[0]
._delegate = System.Windows.Forms.VisualStyles.VisualStyleRenderer.OnUserPreferenceChanging
._syncCtx = System.Windows.Forms.WindowsFormsSynchronizationContext
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.WindowProc(nint hWnd, int msg, nint wParam, nint lParam) Line 961
at Microsoft.Win32\SystemEvents.cs(961)
locals:
msg = 8218
wParam = 0x2f
lParam = 0
[Native to Managed Transition]
[Managed to Native Transition]
Microsoft.Win32.SystemEvents.dll!Interop.User32.DispatchMessageW.____PInvoke|210_0(Interop.User32.MSG* msg)
Microsoft.Win32.SystemEvents.dll!Microsoft.Win32.SystemEvents.WindowThreadProc() Line 1038
at Microsoft.Win32\SystemEvents.cs(1038)
Notes:
- There are no controls being created on background threads (Debugging Windows Forms Application Hangs During SystemEvents.UserPreferenceChanged does not apply)
- At least one control is required on the form (to cause
SystemEvents.UserPreferenceChanging
to be created viaVisualStyleRenderer..cctor
)
Steps to reproduce
Clone either the appbar sample or the minimal SendMessageTimeout
sample. Run the application and close the window which opens. After waiting a few seconds, pause the debugger to observe that the main thread has exited .Net user code and is hung waiting for ???, and that .NET System Events is hung on an invoke to the now destroyed main thread.
The minimal sample can be prepared from an empty winforms project by adding an OnHandleDestroyed
override to Form1
:
[DllImport("USER32.dll", ExactSpelling = true, EntryPoint = "SendMessageTimeoutW", SetLastError = true)]
internal static extern nint SendMessageTimeout(nint hWnd, uint Msg, nint wParam, nint lParam, uint fuFlags, uint uTimeout, IntPtr lpdwResult);
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
// Simulate bad luck in timing or an appbar exiting
var lp = Marshal.StringToHGlobalUni("TapsEnabled");
SendMessageTimeout(0x0000FFFF /* HWND_BROADCAST */, 26 /* WM_SETTINGCHANGE */, 0, lp, 0, 1000, (IntPtr)0);
}
Alternatively, examine the dump file of the hung appbar sample.
About this issue
- Original URL
- State: closed
- Created a year ago
- Comments: 17 (12 by maintainers)
Commits related to this issue
- Incorporated results of https://github.com/dotnet/winforms/issues/9721 — committed to mgaffigan/WpfAppBar by deleted user 9 months ago
Verified this issue on release/7.0 branch of Winforms repo, it was fixed. After closing form, it ends debug mode.
https://github.com/dotnet/winforms/assets/108860782/7e3d92a8-341d-47f0-b9c5-8518221f54ab
@mgaffigan Quick update, we were able to service this 😃 I believe this should be available in 7.0.12 in October.
@lonitra I re-tested it again in .Net 7.0 and .Net 8.0 builds several times, they are definitely different - Same as yours. I’m questioning the results I saw earlier(Maybe changing the target version to 8.0 didn’t work.).
.Net 7.0 behavior: after closing form, applicaion is keeping in debug mode.
.Net 8.0 behavior: after closing form, it ends debug mode. Looks like this issue is not reproduced in .Net 8.