|
dev
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
D=E9j=E0_vu_bug:_The_Matrix_uses_the_.NET_Framework!executed twice. I may have I found the root cause of this. The Matrix encounters a .NET Framework bug. To make a long debugging story short I have been chasing an intermittent lock leak that was not the kind you find in text book. One of my threads is acquiring a lock but is not releasing it (so far nothing exiting). The thread in question runs the following code: lock (this) { // Do A Monitor.Exit(this); try { // Do B } finally { Monitor.Enter(this); } // Do C } In theory, this code is safe and the lock on this should be released no matter if the normal execution flow A, B, and C is performed or if an exception is thrown in either A, B or C. In practice, the lock is not released when my thread live this code. Root Cause I initially thought the lock leak was the result of mixing the C# lock statement with Monitor.Exit/Enter. I quickly ruled this out after I tested that the same leak was happening no matter if I used the lock statement, the calls to Monitor.Enter/Exit or the .NET attribute [MethodImpl(MethodImplOptions.Synchronized)]. Then I put together a simple C# console application to repro and study the behavior of try-finally and was very surprised by what I discover. I hit the Déjà vu bug: the finally clause is some times executed twice!!! Hard to believe, here is the code that proves me right: using System; delegate void Handler(int i); class DejaVu { static void Main(string[] args) { int recurse = 2; try { Handler h = new Handler(Throw); Console.WriteLine("BeginInvoke"); IAsyncResult ar = h.BeginInvoke(recurse, null, null); Console.WriteLine("WaitOne"); ar.AsyncWaitHandle.WaitOne(); Console.WriteLine("EndInvoke"); h.EndInvoke(ar); } catch { Console.WriteLine("Caught exception"); } } private static void Throw(int i) { try { Console.WriteLine("Try({0})", i); if (i == 0) m_object.ToString(); // cause access violation else Throw(i-1); } catch { Console.WriteLine("Catch({0})", i); throw; } finally { Console.WriteLine("Finally({0})", i); } } private static object m_object = null; } The main thread of my application executes the method Throw asynchronously using the .NET Framework asynchronous delegates capability. The method Throw calls itself recursively until the i parameter reaches 0. When 0, the method causes an access violation exception. In theory, this code should produce the following output. BeginInvoke WaitOne Try(2) Try(1) Try(0) Catch(0) Finally(0) Catch(1) Finally(1) Catch(2) Finally(2) EndInvoke Caught exception In practice, you get this one: BeginInvoke WaitOne Try(2) Try(1) Try(0) Catch(0) Finally(0) Catch(1) Finally(1) Catch(2) Finally(0) Finally(1) Finally(2) EndInvoke Caught exception The first two finally clauses Finally(0) and Finally(1) are executed twice! The first when the exception is re-thrown from Catch(0) and Catch(1) handlers respectively (this is normal). The second time when the exception is re-thrown from the Catch(2) handler (this is abnormal). This bug only occurs if the method Throw gets called via an asynchronous delegate. When the Throw method is called directly Throw(recurse); Or via asynchronous delegate Handler h = new Handler(Throw); h(recurse); The behavior and output is correct. The behavior is also correct if you replace the access violation exception caused by m_object.ToString() with an explicit exception like throw new ArgumentException(); More testing showed me that the bug occurs only in the following situation: 1. The method is called via asynchronous delegate, 2. The method itself or any of sub methods throws an Win32 exception (I have only tested access violation and divide by zero but I bet the other would cause the same issue), 3. The exception is caught and re-thrown. You are going to tell me that this is a rare situation and I would agree but I hit this anyway. Murphy law! Workaround The workaround I am using is simple: I always make sure that no exceptions ever leave a method invoked via asynchronous delegates. To this end I am using a mediator method that catches all exceptions and return them to the caller in a custom fashion. In the snippet below the mediator just writes the exception to the console. delegate void MediatorHandler(ThrowHandler h, int i); static void Main(string[] args) { int recurse = 2; try { Handler h = new Handler(Throw); MediatorHandler mh = new MediatorHandler(Mediator); Console.WriteLine("BeginInvoke"); IAsyncResult ar = mh.BeginInvoke(h, max, null, null); Console.WriteLine("WaitOne"); ar.AsyncWaitHandle.WaitOne(); Console.WriteLine("EndInvoke"); mh.EndInvoke(ar); } catch { Console.WriteLine("Caught exception"); } } private static void Mediator(Handler h, int i) { try { h(i); } catch (Exception ex) { Console.WriteLine("Mediator caught exception {0}", ex); } } Since the exception never leave the mediator, the finally clauses are called once and only once. Finally :-) I have been looking on the Web and Newsgroups for references to this issue but haven't found any. Even though this should be a rare case, I doubt I am the only one to have encountered this problem. I have tested this against the .NET Framework 1.1 SP1. I plan to test it against .NET Framework 2 beta 2 ASAP. <laurentfrompa***@gmail.com> wrote:
<snip> > I have been looking on the Web and Newsgroups for references to this It does seem very odd. I've seen something similar on the compact > issue but haven't found any. Even though this should be a rare case, > I doubt I am the only one to have encountered this problem. framework (not quite the same thing, but similarly wacky exception situation) but not on the main framework. > I have tested this against the .NET Framework 1.1 SP1. I plan to test I've just done so, and your test code does the expected things.> it against .NET Framework 2 beta 2 ASAP. -- Jon Skeet - <sk***@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too |
|||||||||||||||||||||||