Wednesday, October 21, 2009

 

WinForms App Locked Up - Stack shows OnUserPreferenceChanged leading to a WaitOne after unlocking workstation

Today I had an interesting bug to track down in which an application kept locking up after I locked and then unlocked my workstation. I was able to attach the debugger, do a "Break All" and then use the Thread and Call Stack windows get a trace stack. Somewhere down in the .NET framework, the Microsoft.Win32.SystemEvents.OnUserPreferenceChanged method raised the UserPreferenceChanged event, and an event handler attached to that event ultimately lead to WaitOne which was blocking indefinitely on the UI thread. The top of the callstack looked like this:


[In a sleep, wait, or join]
mscorlib.dll!System.Threading.WaitHandle.WaitOne(long timeout, bool exitContext) + 0x2f bytes
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) + 0x25 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle = {System.Threading.ManualResetEvent}) + 0x77 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) + 0x393 bytes
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) + 0x50 bytes
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) + 0x5f bytes
System.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization = true, object[] args = {object[2]}) + 0x62 bytes
System.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization = true, object key = {object}, object[] args = {object[2]}) + 0x10f bytes
System.dll!Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x77 bytes
System.dll!Microsoft.Win32.SystemEvents.WindowProc(System.IntPtr hWnd = 9111196, int msg = 8218, System.IntPtr wParam = 47, System.IntPtr lParam = 188618224) + 0x2e0 bytes
 

Presumably, whatever was suppose to signal the WaitOne was also on the main UI thread, so there was a deadlock. Whatever was wrong was apparently happening deep-down in the framework, as there was nothing in the application source code that ever attached an event handler to the UserPreferenceChanged event.

After Googling for some answers, it became apparent that there was probably a threading problem somewhere in the application (as if you couldn't have assumed that one?). Apparently, all top-level controls automatically register for the UserPreferenceChanged event so that they can update themselves when the WM_SETTINGCHANGE Windows message is received to indicate that desktop properties (color, theme, etc.) have changed. And, when SystemEvents raises an event to a partiuclar event handler, it checks to see if it is already on the thread appropriate for the object receiving the event; if it is not, it Invokes over to the appropriate thread. But, if the thread onto which the event handler is invoked does not have a message pump, a lockup occurs (summarized from From kbalertz.com) because Invoke waits forever.

Even with that bit of knowledge, it became difficult to track down the problem. But, "Burn me now, or burn me later" -- I eventually deteremined that it was caused by a cross-threading issue that occurred earlier in the application; for some reason, Visual Studio did not detect it as a cross-thread exception. There was a spot in code that used a System.Timers.Timer, and the callback for the Timer was not properly marshalling calls over to the UI thread before updating some UI properties. Somehow, that cross-threading issue "in the past" affected the ability for a some control to process the UserPreferenceChanged event in the future.

Comments:
I wish I had known this yesterday!As it turns out, Aaron Lerch (who happens to sit four doors down the hall from me) has already written an excellent post on how to tackle problems just like this.
 
Post a Comment





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]