Thursday 29 March 2012

When a Process dies

In the end, software is not unlike biological entities, sooner or later all gets to an end. I've been pondering a bit about how a process can be finished off, what happens at that point, what code we can assume is run, how resources are cleaned up... and so on... and I think it's a good idea to put together my findings and conclusions. This will be focused on the Windows OS family and the .Net platform.

First of all, you should read this excellent article, almost all you need to know is there. In turn, part of the information there is taken from here.

The distinction between cooperative and abnormal process shutdown seems really important, but in the end probably is not that much. One of the main concerns would be whether the resources used by the application will be released or not. Well, given that we place our clean up code in finally blocks and in finalizers, the fact that even in a cooperative shutdown the finally blocks are not run, and the finalizers are tried, but they won't run if they take too long, could seem worrying. Actually, it's not that worrying, because when a process finishes (either gracefully or abruptly) the OS will take care of cleaning up its resources (decrease the reference count in the corresponding Kernel objects... and so on) as you can read here. Most times, the only code that is inside our finalizers is code to release native resources (CloseHandle...), so it seems like that task will be carried out by the OS anyway. Likewise, the situation is similar for finally blocks, usually we put resources release code there (think of a using construct, that in the end comes down to a try-finally), as those resources will end up being released by the OS, so that does not seem like a problem.

With this in mind, one could think, why does the runtime run the Finalizers in a cooperative shutdown if anyway the OS is going to release resources? Well, in some cases I think your finalizer could do something more than just releasing the native resources (write a log, send a last message over the network, try to flush a buffer). I'm saying "I think" cause almost in every article that you can come across, when they talk about finalization and dispose they only talk about releasing resources.
Contrary to what I initially thought (this article has been really helpful), it should be safe to use a Managed, non finalizable object from your finalization code, what you have to refrain from is using Finalizable objects, cause we don't know in what order objects are finalized, so that object could already have been finalized and therefore would be unable to do its work properly. Anyway, also in this article they stick to the "release resources" mantra:

Generalizing this principle, in a Dispose method it’s safe to clean up all resources that an object is holding onto, whether they are managed objects or native resources. However, in a finalizer it is only safe to clean up objects that are not finalizable, and generally the finalizer should only be releasing native resources.

There are a few more things I've learned that I'd like to add here

  • How does Task Manager kill processes? First of all, you'll have noticed that sometimes killing a process from the Applications tab fails, but killing it from the Processes tab works OK. As it's well explained here, different mechanisms are used in both cases. Killing it from the Process tab does a call to TerminateProcess, that will finish off the process without giving it any chance to run any extra code. Well, it's interesting to see that as explained here, the OS will try to complete all pending IO operations before the process exits.

    The TerminateProcess function is used to unconditionally cause a process to exit. The state of global data maintained by dynamic-link libraries (DLLs) may be compromised if TerminateProcess is used rather than ExitProcess. TerminateProcess initiates termination and returns immediately. This stops execution of all threads within the process and requests cancellation of all pending I/O. The terminated process cannot exit until all pending I/O has been completed or canceled. A process cannot prevent itself from being terminated.

  • How does sending Ctrl-C to a console application differ from the above?. The main difference is that your console application can have registered a handler routine via SetConsoleCtrlHandler to do some extra processing. The default handler for the Ctrl-C signal just calls to ExitProcess. This question is also worth a read.

No comments:

Post a Comment