Alois Kraus

blog

  Home  |   Contact  |   Syndication    |   Login
  104 Posts | 8 Stories | 292 Comments | 162 Trackbacks

News



Article Categories

Archives

Post Categories

Image Galleries

Programming

All good things have to end even your perfectly working managed executable. But do you know in what circumstances the CLR will terminate your program and much more importantly when do you have a chance to run some finalizers and shutdown code? As always in live it depends. Lets have a look at the reasons why program execution can be terminated.

Ways to terminate an application:
  1. The last  foreground thread of an application ends. The thread which entered the Main function is usually the only  foreground thread in your application.
  2. If you run an Windows Forms application you can call System.Windows.Forms.Application.Exit() to cause Application.Run to return.
  3. When you call System.Environment.Exit(nn)
  4. Pressing Ctrl-C or Ctrl-Break inside a Console application.
  5. Call System.Environment.FailFast (new in .NET 2.0) to bail out in case of fatal execution errors.
  6. An unhanded exception in any thread regardless if it is managed or unmanaged.
  7. Unmanaged exit calls within unmanaged code.
What exactly happens during shutdown is explained by an excellent article of Chris Brumme.

Shutdown Process
Generally speaking we can distinguish between two shutdown types: Cooperative and Abnormal. Cooperative means that you get some help from the CLR (execution guarantees, callback handlers, ...) while the other form of exit is very rude and you will get no help from the runtime.
During a cooperative shutdown the CLR will unload the Application Domain (e.g. after leaving the Main function). This involves killing all threads by doing a hard kill as opposed to the "normal" ThreadAbortException way where the finally blocks of each thread are executed. In reality the threads to be killed are suspended and never resumed. After all threads sleep the pending finalizers are executed inside the Finalizer thread. Now comes a new type of finalizers into the game which was introduced with .NET 2.0. The Critical Finalizers are guaranteed to be called even when normal finalization did timeout (for .NET 2.0 the time is 40s ) and further finalizer processing is aborted. If you trigger an exception inside a finalizer and it is not catched you will stop any further finalization processing, including the new Critical Finalizers. This behavior should be changed in a future version of the CLR. What are these "safe" finalizers good for when an "unsafe" finalizer can prevent them from running? There is already a separate critical finalizer queue inside the CLR which is processed after the normal finalization process did take place.

Normal Shutdown
A fully cooperative shutdown is performed in case of 1, 2 and 3. All managed threads are terminated without further notice but all finalizers are executed. No further notice means that no finally blocks are executed while terminating the thread.

Abnormal Shutdown
The bets are off and no CLR event is fired which could trigger a cleanup action such as calling finalizer, or some events. Case 4 can be converted into a normal shutdown by the code shown below. Case 6 can be partially handled inside the AppDomain.UnhandledException. The other ones (Environment.FailFast and unmanaged exits) have no knowledge of the CLR and perform therefore a rude application exit. The only exception is the Visual C++ (version 7+) Runtime which  is aware of the managed world and calls back into the CLR (CorExitProcess) when you call the unmanaged exit() or abort() functions.

Interesting Events
All these events are only processed when we are in a cooperative shutdown scenario. Killing your process via the Task Manager will cause a rude abort where none of our managed shutdown code is executed.
  • The AppDomain.DomainUnload  event is not called in your default AppDomain. When you exit e.g. the Main function the default AppDomain is unloaded but your event handler will never be called. The DomainUnload handler is called from an arbitrary thread but always in the context of the domain that is about to be unloaded.
  • AppDomain.ProcessExit is called when the CLR does detect that a shutdown has to be performed. You can register in each AppDomain such a handler but be reminded that after the DomainUnload event was fired you will never see a ProcessExit event because your AppDomain is already gone. These two events are mutually exclusive. When you kill the process via 
  • AppDomain.UnhandledException is called only in the default AppDomain. You can register this handler only in the "root" AppDomain which is the first one created for you by the CLR. Hosting of other AppDomains in a plugin architecture can cause unexpected side effects if the loaded modules register this handler and expect it to be called. When the handler is left the process finally terminates which cannot be prevented in .NET 2.0 except if you set a compatibility flag inside the runtime settings your App.config file.
     <legacyUnhandledExceptionPolicy enabled="1"/>
Ctrl-C/Ctrl-Break
The default behavior of the CLR is to do nothing. This does mean that the CLR is notified very late by a DLL_PROCESS_DETACH notification in which context no managed code can run anymore because the OS loader lock is already taken. We are left in the unfortunate situation that we get no notification events nor are any finalizers run. All threads are silently killed without a chance to execute their catch/finally blocks to do an orderly shutdown. I said in the first sentence default because there is a way to handle this situation gracefully. The Console class has got a new event member with .NET 2.0: Console.CancelKeyPress. it allows you to get notified of Ctrl-C and Ctrl-Break keys where you can stop the shutdown (only for Ctrl-C but not Ctrl-Break). If you want to do a coordinated shutdown inside the handler you will run into the gotcha that calling Environment.Exit does not trigger any finalizers. When we do have other plans to exit the process we need to spin up a little helper thread inside the event handler which will then call Environment.Exit. Voila our finalizers are called.


Graceful Shutdown when Ctrl-C or Ctrl-Break was pressed.

       static void GraceFullCtrlC()

        {

            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)

            {

                if( e.SpecialKey == ConsoleSpecialKey.ControlBreak )

                {

                    Console.WriteLine("Ctrl-Break catched and translated into an cooperative shutdown");

                    // Envirionment.Exit(1) would NOT do a cooperative shutdown. No finalizers are called!

                    Thread t = new Thread(delegate()

                                    {

                                        Console.WriteLine("Asynchronous shutdown started");

                                        Environment.Exit(1);

                                    });

                    t.Start();

                    t.Join();

                }

                if( e.SpecialKey == ConsoleSpecialKey.ControlC )

                {

                    e.Cancel = true; // tell the CLR to keep running

                    Console.WriteLine("Ctrl-C catched and translated into cooperative shutdown");

                    // If we want to call exit triggered from out event handler we have to spin

                    // up another thread. If somebody of the CLR team reads this. Please fix!

                    new Thread(delegate()

                    {

                        Console.WriteLine("Asynchronous shutdown started");

                        Environment.Exit(2);

                    }).Start();

                }

            };

            Console.WriteLine("Press now Ctrl-C or Ctrl-Break");

            Thread.Sleep(10000);

        }




Unhandled Exceptions

It is very easy to screw up your application with an abnormal application exit. Lets suppose the following code:

        static void RudeThreadExitExample()

        {

            // Create a new thread

            new Thread(delegate()

            {

                Console.WriteLine("New Thread started");

                // throw an unhandled exception in this thread which will cause the application to terminate

                // without calling finalizers and any other code.

                throw new Exception("Uups this thread is going to die now");

            }).Start();

        }

We spin up an additional thread and create an unhandled exception. It is possible to register the AppDomain.Unhandled exception handler where you can e.g. log the exception but nonetheless your application will be rudely terminated. No finalizers are executed when an unhandled exception occurs. If you try to fix this by calling Environment.Exit inside your handler nothing will happen except that the ProcessExit event is triggered. We can employ our little Ctrl-C trick here and force an ordered cooperative shutdown from another thread.

Cooperative Unhandled Exception Handler (Finalizers are called)

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)

        {

            Console.WriteLine("Unhandled exception handler fired. Object: " + e.ExceptionObject);

            // Prepare cooperative async shutdown from another thread

            Thread t = new Thread(delegate()

                                {

                                    Console.WriteLine("Asynchronous shutdown started");

                                    Environment.Exit(1);

                                });

            t.Start();

            t.Join(); // wait until we have exited

        }

This way you can ensure that your application exits in a much cleaner way where all finalizers are called. What I found interesting during my investigation that there seems no time limit to exist how long I want to run the unhandled exception handler. It is therefore possible that you have several unhandled exception handlers running at the same time. An ordered application shutdown can be quite twisted if you want to do it right. Armed with the knowledge from this post you should have a much better feeling what will happen when things go wrong now.
posted on Monday, October 30, 2006 1:12 AM

Feedback

# re: Cooperative Application Shutdown with the CLR 11/9/2006 3:51 AM Mike Liddell
Excellent information . and I was looking for something like this just the other day.

I must say I am very surprised that so many situations can cause finally blocks to be ignored.

In particular, the .NET 2.0 WinForms NotifyIcon doesn't dispose itself correctly and leaves the icon in the system tray until a mouse hover forces a cleanup. During orderly shutdowns, the Dispose can be called, but in disorderly shutdowns, it appears nothing can be done. It seems quite a worry that TaskManager kill etc doesn't allow .NET code to clean up after itself.

# re: Cooperative Application Shutdown with the CLR 6/30/2008 4:47 AM Todd Beaulieu
Great article. Thank you.

# re: Cooperative Application Shutdown with the CLR 7/8/2009 4:34 PM Senthil
This is a very useful article. Thanks

# re: Cooperative Application Shutdown with the CLR 2/3/2010 11:10 AM FastAl
@Mike Liddell,
Task manager is supposed to kill an app right off- otherwise the app could do things that the user killing it doesn't want (including cleanup; the user is warned or should know better at that level). Loss of power of course precludes cleanup as well ;-)

The problem with lingering icons in the task tray is an issue with the task tray. Many sections of windows 'auto-cleanup' after a program (memory, GDI objects, etc) behind the scenes. Windows 3.1 for example did not do this well at all and suffered for it.

The task tray needs to defend itself the same way (haven't tried what it does in vista or 7 but I'd imagine you weren't wondering about that in 2006).
-Al

# re: Cooperative Application Shutdown with the CLR 2/2/2012 8:34 AM Lukasz
You said "The DomainUnload handler is called from an arbitrary thread". Can you elaborate on this please? Is it possible that this event will be for example called from a finalizer thread? It would be great if the answer could be backed by linking to official docs.

# re: Cooperative Application Shutdown with the CLR 9/12/2013 9:37 AM grwww
There is great deal of history that points out that unix has done so many things right, 10 years or more before windows did them all wrong.

if( (pid = fork()) == 0 ) {
execl("/path/to/something", "something", arg1, null );
perror("execl /path/to/something failed");
exit(-1);
}
signal(SIG_TERM, onterm);
...
void onterm( int sig ) {

kill(pid, SIG_TERM );
}

is all it takes to make sure that a simple kill on a process makes sure that a dependent child process can be cleaned up too.

Microsoft's CLR is just a pile of code trying to be helpful, but failing at all of the things that real programs actually need to be resilient and bullet proof.

# re: Cooperative Application Shutdown with the CLR 9/15/2013 6:50 AM Alois Kraus
You do not need to fork a process to terminate it. The interesting thing is what do you do if you have several threads up and running when you receive the shutdown signal? Your empty handler is not showing the solution but the problem that proper resource management during shutdown is hard.
I am not too deep into unix anymore but in C and C++ cleaning up singletons which depend on each other was very problematic there and resulted in many segfaults during shutdown. Especially when you did depend on some library which you do not have under control and does at some point also clean up (e.g. the ACE library was quite good 10 years ago).

Post A Comment
Title:
Name:
Email:
Comment:
Verification: