WTF Reentrancy

System.Windows.Forms.dll!System.Windows.Forms.Control.OnPaint(System.Windows.Forms.PaintEventArgs e) + 0x73 bytes   
System.Windows.Forms.dll!System.Windows.Forms.Control.PaintWithErrorHandling(System.Windows.Forms.PaintEventArgs e = {ClipRectangle = {System.Drawing.Rectangle}}, short layer, bool disposeEventArgs = false) + 0x9a bytes 
System.Windows.Forms.dll!System.Windows.Forms.Control.WmPaint(ref System.Windows.Forms.Message m) + 0x1d4 bytes 
System.Windows.Forms.dll!System.Windows.Forms.Control.WndProc(ref System.Windows.Forms.Message m) + 0x33e bytes 
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) + 0x10 bytes    
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0x31 bytes  
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg = 15, System.IntPtr wparam, System.IntPtr lparam) + 0x5a bytes  
[Native to Managed Transition]

mscorlib.dll!System.Threading.ReaderWriterLock.AcquireReaderLock(int millisecondsTimeout) + 0x5 bytes

If you see a stack trace like that (perhaps with Monitor.Enter in place of ReaderWriterLock.AcquireReaderLock), .NET/Win32/COM just gave you the finger. You made the mistake of acquiring a lock on the GUI thread (or any STA thread) in a .NET program. Doing that can hang the GUI...or at least, that's what would happen in a world that makes sense. Unfortunately, you left that world behind when you started writing .NET/Win32 software.

Let's back up a bit here. The first thing to note is that the "GUI thread" in .NET (i.e. the thread that everything happens in unless you explicitly request otherwise) uses the STA threading model. For an explanation of threading models, see Larry Osterman's post on the subject. The short version is that COM objects (What, you thought you left COM behind when you switched to .NET? Silly fool!) basically need to run in a STA thread. However, STA threads are required to pump messages. Thus, acquiring a lock (which can block message pumping indefinitely) is a Bad Thing.

.NET tries to be helpful here by ensuring that messages get pumped, even while waiting for a lock. This means that your GUI thread can do work while waiting for a lock. Awesome, right? Well, it's not so awesome when that work involves calling the method in which you attempted to acquire a lock. If you're lucky, you will get a deadlock. If not, you will see something completely inexplicable at some point down the line. I call this "WTF Reentrancy" because those are the two words that rise to the surface of any sane mind that sees a reentrant method call from the same thread. This is, by the way, the very thing that makes POSIX signals suck. I have seen this most often with locks acquired in Win32 control paint methods (Yes, I know that's a very bad place to acquire a lock.) and methods called from there. It gets even more fun when things like OpenGL get involved. And by "fun" I mean "sanity-devouring".

Now, since we're already doing something questionable, it's OK for things to go wrong sometimes. I understand that. However, brief hangs or deadlock caused by lack of message pumping would be much, much easier to diagnose than WTF Reentrancy, especially since the latter can break things in subtle ways far from the source of the problem, while the former breaks things immediately in a fairly obvious way. Whoever thought this "feature" up clearly didn't think hard enough.

Somehow, I don't think you thought your cunning plan all the way through.

Oh, and here is an explanation of how COM fits in.