question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Hang during exit if `WM_SETTINGCHANGE` is received

See original GitHub issue

.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:

  1. 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, ...))
  2. The main thread begins shutting down (disposing controls, form, etc…)
  3. The “.NET System Events” thread receives WM_SETTINGCHANGE and Invokes to the main thread
  4. Since the main thread has not shutdown, the test at WindowsFormsSynchronizationContext.Send:82 succeeds
  5. The “.Net System Events” thread enters a WaitHandle
  6. The main thread, having had its main form closed, exits the message loop and Main returns
  7. 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:

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.

Issue Analytics

  • State:closed
  • Created a month ago
  • Comments:7 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
Olina-Zhangcommented, Aug 18, 2023

@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. Net7 0Behavior

.Net 8.0 behavior: after closing form, it ends debug mode. Looks like this issue is not reproduced in .Net 8. Net8 0Behavior

0reactions
elachlancommented, Aug 21, 2023

@merriemcgaw did the team want to backport this?

Read more comments on GitHub >

github_iconTop Results From Across the Web

NET 4.0 and the dreaded OnUserPreferenceChanged Hang
a Control that was constructed off the UI thread... Yes, that is a good way to trigger this problem. The underlying problem is...
Read more >
Why does SystemParametersInfo hang when I pass ...
If you pass the SPIF_SENDCHANGE flag to the SystemParametersInfo function, it will broadcast the WM_SETTINGCHANGE message with the wParam ...
Read more >
access violation in WM_SETTINGCHANGE with lParam ...
Using the "Switch User" feature of Windows and ran into an issue with a WM_SETTINGCHANGE getting fired at IslandWindow.cpp with wParam == 0 ......
Read more >
Application hangs When XtraForm created in another ...
Now, if you were to run this, open and close the form, and then trigger a windows WM_SETTINGCHANGE message, you should notice the...
Read more >
Mysterious hang or the great deception of InvokeRequired
Windows procedure on the main GUI thread receives WM_SETTINGCHANGE message. The code in SystemEvents class raises UserPreferenceChanged event.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found