7

Enabling line //1 below will make the program crash without "proc exit" being printed, but in case of line //2, "proc exit" will be printed. "unhandled" btw gets printed in both cases.

Why the difference, and what are the rules in general? Obviously killing an app using e.g. the task manager will prevent "proc exit" from being printed, but other than that, what are the cases it doesn't get printed?

static void Main(string[] args)
{
    Thread.GetDomain().UnhandledException +=
        (sender, eventArgs) => Console.WriteLine("unhandled");
    AppDomain.CurrentDomain.ProcessExit +=
        (sender, eventArgs) => Console.WriteLine("proc exit");
    //1 new Thread(_ => { throw new Exception(); }).Start();
    //2 ThreadPool.QueueUserWorkItem(_ => { throw new Exception(); });
    Thread.Sleep(1000);
    return;
Evgeniy Berezovsky
  • 18,571
  • 13
  • 82
  • 156

2 Answers2

5

Please take a look at this blog post: AppDomain.ProcessExit is not guaranteed to be called. Quoted from the post:

The AppDomain.ProcessExit is not guaranteed to be called. It's pretty resilient and will deal with common things you may cause from low-trust IL (exceptions, out-of-memory, etc), but there are some things (like rude process shutdown), that an inprocess event can never be resilient against.

(...)

The callback is invoked from within the process. If the process rudely exits, the callback would not be invoked. Common sources for rudely exiting include:

  1. Killed externally via TaskManager or kernel32!TerminateProcess.
  2. Stackoverflow handler consumes past the guard page.

The ProcessExit event is like telling somebody "please telephone me that your about to die". If death comes quickly enough, they may not get the message out.

In fact, I have tried out your code and it does never print "proc exit" when uncommenting line 1 or even line 2! (I have tried compiling against all .NET versions, in Visual Studio 2013). Of course it does print it when no exceptions are thrown and the process exits normally. (EDIT: I see the "proc exit" message if I compile the code in Release mode, but not when I compile in Debug mode)

As a side note, here is a suggested refactor for your code (not fully tested and probably incomplete, but you get the idea):

static void Main(string[] args)
{
    Thread.GetDomain().UnhandledException +=
        (sender, eventArgs) => Exiting((Exception)eventArgs.ExceptionObject);
    AppDomain.CurrentDomain.ProcessExit +=
        (sender, eventArgs) => Exiting(null);
    //1 new Thread(_ => { throw new Exception(); }).Start();
    //2 ThreadPool.QueueUserWorkItem(_ => { throw new Exception(); });
    Thread.Sleep(1000);
    return;
}

static void Exiting(Exception exception)
{
    //Put common cleanup code here (or at the end of the method)

    if(exception == null)
    {
        Console.WriteLine("normal proc exit");
    }
    else
    {
        Console.WriteLine("unhandled exception: " + exception.GetType().Name);
    }
}
Konamiman
  • 49,681
  • 17
  • 108
  • 138
  • I can reliably reproduce the "proc exit" behavior I stated in VS2013, .net 4.5.1, Release-Mode, and running via Ctrl-F5 (no debugging). Fwiw, the "proc exit" gets printed after the Windows error dialog pops up. – Evgeniy Berezovsky Dec 20 '13 at 00:11
  • Btw when you tested `//2`, you did comment out `//1` again, right? – Evgeniy Berezovsky Dec 20 '13 at 00:39
  • 1
    Yes, I tested 2 without 1. I tested by running the compiled executable, not inside VS. Anyway I was compiling in debug mode... in release mode the message does indeed appear. That's interesting. – Konamiman Dec 20 '13 at 07:56
  • 2
    Link is broken. – chtenb Sep 01 '21 at 12:16
4

There is no difference between the two cases, hard to explain why you'd see one.

With the code as written, the AppDomain.ProcessExit event should never be raised. The normal thing should happen, the CLR passes the exception to the operating system and the Windows Error Reporting crash dialog pops up. It trundles for a while to check with Microsoft servers if the exception is a known reason for programs to crash (it won't be), then terminates the program when the user dismisses the dialog. So no ProcessExit event.

You can certainly write your code so that ProcessExit will be raised. It requires you not leaving it up to the operating system to take care of it. You must shut down the program yourself. Which you do by explicitly terminating the program yourself with your AppDomain.UnhandledException event handler. Like this:

    Thread.GetDomain().UnhandledException += (s, e) => {
        var ex = (Exception)e.ExceptionObject;
        Console.WriteLine(ex.ToString());
        Environment.Exit(System.Runtime.InteropServices.Marshal.GetHRForException(ex));
    };

You do have a choice on how you terminate the program. I showed Environment.Exit() but you could also consider Environment.FailFast() to ask for an instant abort without running any more code. That's safer but of course you won't get ProcessExit either.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks! This looks like a reliable way to deal with the issue, and I'll use it. Still doesn't explain the issue... – Evgeniy Berezovsky Dec 20 '13 at 00:15
  • Note to editor: using e.Terminating is silly, it is always true since .NET 2. Which changed the default CLR host policy for unhandled exceptions to "terminate program". The .NET 1.x policy of was a disaster. Reverted. – Hans Passant Dec 20 '13 at 00:41
  • The [docs](http://msdn.microsoft.com/en-us/library/system.unhandledexceptioneventargs.isterminating) say: `Beginning with the .NET Framework version 2.0, this property returns true for most unhandled exceptions, unless an application compatibility flag is used to revert to the behavior of versions 1.0 and 1.1.` Which suggests that there are cases, even without setting the compatibility flag, where `e.Terminating == false`. – Evgeniy Berezovsky Dec 20 '13 at 00:46
  • 1
    The docs say *nothing* about what's going to happen to your app when the user overrides the policy. And it is never anything good, you didn't test your app that way. No programmer can ever write solid code that knows how to deal with unhandled exceptions that leaves the program in an unguessable state. You didn't test your app in that state, there is no way to reason what can go wrong when you allow it to happen anyway. You didn't test the thousands of ways that this could happen. – Hans Passant Dec 20 '13 at 00:54
  • 2
    I'm talking about the possibility that the CLR might not exit the process, with or without setting the compatibility flag, if the docs are to be trusted. Not checking for `e.IsTerminating` will cause the process to exit when it would not have exited normally. I don't want to make such a significant change to an existing app that I don't know if it relies on not being shut down in certain unhandled cases, whether that's smart or not. I just want to make sure that if it does crash, it doesn't do so without saying goodbye and telling me why. – Evgeniy Berezovsky Dec 20 '13 at 01:11