2

Given that:

  • taking a lock while in a finalizer can cause deadlocks
  • finalizers can throw exceptions

Is it safe to take a lock while inside an unhandled exception handler or can the code below cause deadlocks?

static void Main(string[] args)
{
     AppDomain.CurrentDomain.UnhandledException += 
         new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
     //do other stuff
}

private static object loggingLock = new object();

static void CurrentDomain_UnhandledException(
    object sender, 
    UnhandledExceptionEventArgs e)
{
    lock (loggingLock)
    {
        //log the exception
    }
}
FishBasketGordo
  • 22,904
  • 4
  • 58
  • 91
Yaur
  • 7,333
  • 1
  • 25
  • 36
  • 1
    Not a direct answer, but perhaps you can try moving the `loggingLock` object into your log class (where the work is actually done). – Matt Klein Sep 27 '12 at 21:08
  • 1
    really it is, but I'm trying to keeping the code as simple as possible for the sake of posting on SO – Yaur Sep 27 '12 at 21:25
  • Gotcha... that makes sense. I'd post an answer but I don't know (which is why I was watching this to begin with) – Matt Klein Sep 27 '12 at 21:37
  • 1
    Using *lock* in an UnhandledException event handler doesn't make sense. There's only *one* unhandled exception that terminates your app, the CLR already serializes disaster. – Hans Passant Sep 27 '12 at 22:14
  • true, but again it is really inside of the log call so its much harder to get rid of then simply removing a lock statement in the handler its self. – Yaur Sep 27 '12 at 22:57

3 Answers3

3

Given that:

  • taking a lock while in a finalizer can cause deadlocks
  • finalizers can throw exceptions

Edit Turns out, exceptions thrown in a finalizers are fatal by definition:

doc: If Finalize or an override of Finalize throws an exception, and the runtime is not hosted by an application that overrides the default policy, the runtime terminates the process and no active try-finally blocks or finalizers are executed. This behavior ensures process integrity if the finalizer cannot free or destroy resources.

See also c# finalizer throwing exception?

Note: even though an exception may have originated from within a function, this does not mean that it would be handled in the context of that function. In fact, the stack would have been unwound:

doc: An exception is unhandled only if the entire stack for the thread has been unwound without finding an applicable exception handler, so the first place the event can be raised is in the application domain where the thread originated.


I don't see why it would not be safe to lock. (The usual caveats would apply: don't do blocking operations while holding the lock...).

However, you may want to think twice about reentrancy and infinite recursion here:

  • how would you respond to errors while logging? The lock would be acquired by definition, because the thread already held it. But is the logging code reentrant? I.e.: would invoking another log operation mess up state for the ongoing (failing/failed) operation? Would logging even be possible?

    --> if reentrancy is not permittable (or requires special actions (like, logging elsewhere) you need an explicit 'inLoggingOperation' flag even though the lock is acquired, since single-threaded re-entrancy is nog prevented by the lock

  • Minor point: If your logging isn't entirely exception-proof you may get into trouble when already in CurrentDomain.UnhandledException (AFAICT the docs do not describe what happens when an exception is raised in the event handler).

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I've dug up some more relevant information on the specific situation with finalizers: see edits – sehe Sep 27 '12 at 21:53
  • from the linked docs "The common language runtime suspends thread aborts while event handlers for the UnhandledException event are executing." is the part that makes me worry. – Yaur Sep 27 '12 at 22:58
  • @Yaur Could you explain specifically what worries you about that? – sehe Sep 27 '12 at 23:04
  • The question is: are threads that are holding locks still in a state where they are able to release the locks (while finalization is occurring they may not be). After some testing it seems like it isn't 100% safe but that it doesn't ever make the situation worse than it is already. – Yaur Sep 28 '12 at 05:13
  • Hehe after "are threads ... still ... able to release" I was gonna say: in the cases where they don't, it doesn't matter anymore. Turns out, you said something similar in the next sentence. I'd say I agree :) – sehe Sep 28 '12 at 06:41
0

Well, I did some hunting and I found this from the MSDN:

A lock statement of the form

lock (x) ...

where x is an expression of a reference-type, is precisely equivalent to

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

except that x is only evaluated once.

While a mutual-exclusion lock is held, code executing in the same execution thread can also obtain and release the lock. However, code executing in other threads is blocked from obtaining the lock until the lock is released.

8.12 The lock statement

So, the lock will be released if an exception is thrown from within the lock because of the finally statement.

With that info, I would be 95% sure that you won't deadlock by attempting to lock from within your CurrentDomain_UnhandledException method. If someone knows otherwise I would love to here from them (and references would be nice, too).

Matt Klein
  • 7,856
  • 6
  • 45
  • 46
  • You're absolutely right, the semantics of `lock` ***always*** guarantee proper release (unless the process disappears). However, this is not the main question I distilled from the OP – sehe Sep 27 '12 at 21:45
  • A good point. The exact question that I understood was "Is it safe to take a lock while inside an unhanded exception handler..." and I'm pretty sure the answer is yes. The why has to do with what `lock(...)` expands into. – Matt Klein Sep 27 '12 at 21:48
  • Mmm. Not sure I agree. The "why is it safe" has to do with when the UnhandledException handler could legally be invoked (as well). I think you may be thinking of the reverse: "Why would it be safe to throw exceptions that would leave the scope of surrounding `lock()` clauses?". This is certainly explained correctly in your answer. – sehe Sep 27 '12 at 21:58
  • It seems to me that the use case here is that you would get a `lock(x)` somewhere in your code, and also have a `lock(x)` that is used inside your `CurrentDomain_UnhandledException` method. So the question of deadlocking can be answered by following the program execution. 1) perform `Lock(x)` somewhere in code 2) throw exception in the locked code 3) `try\finally` is hit as the stack unwinds 4) `finally` block is executed 5) lock on x is removed 6) `UnhandledException` is invoked. --Am I missing something or not properly understanding this scenario? Thanks for any feedback! – Matt Klein Sep 27 '12 at 22:32
  • 1
    You assume that the other lock is also attained by using the `lock()` syntax. This is not necessarily the case – sehe Sep 27 '12 at 22:48
0

for posterity... some test code:

class Program
{
    static AutoResetEvent e1 = new AutoResetEvent(false);
    static AutoResetEvent e2 = new AutoResetEvent(false);
    private static object lockObject = new object();

    private static void threadProc()
    {
        lock (lockObject)
        {
            e1.Set();
            e2.WaitOne();
            Console.WriteLine("got event");
        }
    }

    private static int finalized = 0;

    public class finalizerTest
    {

        ~finalizerTest()
        {
            try
            {
                throw new NullReferenceException();
            }
            finally
            {
                Interlocked.Increment(ref finalized);
            }
        }
    }

    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem((a) => threadProc());
        e1.WaitOne();
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        {
            finalizerTest f = new finalizerTest();
        }

        //uncommenting this will cause logging to happen as expected
        /*
        while (finalized == 0)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
         */

    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("in handler -- pre lock");
        e2.Set();

        lock (lockObject)
        {
            Console.WriteLine("in handler");
        }
    }
}

What happens is that if finalizerTest is finalized because the application leaves main the output reads:

in handler -- pre lock

if however it is finialized because of a GC.Collect/WaitForPending finalizers it reads:

in handler -- pre lock
got event
in handler

What this means is that in the specific case of exceptions thrown from finalizers while the application is going down you might not be able to get your lock, but in this case the application and the finalization queue is already in serious trouble and locking doesn't make it worse.

In every other test I could think of everything happens as expected, threadProc wakes up and logging occurs.

Yaur
  • 7,333
  • 1
  • 25
  • 36