Bug or feature? std.c.stdlib.exit() breaks RAII

Jonathan M Davis jmdavisProg at gmx.com
Fri Dec 30 10:41:30 PST 2011


On Friday, December 30, 2011 10:45:43 Ashish Myles wrote:
> Ok, now there are two issues here:
> IMPLEMENTATION: Implementation of a safe_exit() without an explicit
> Exception seems to be easy to do at the language level for a
> single-threaded program -- you simply have a hidden/system class like,
> say, __SystemException from which Exception derives that be comes the
> base class of all throwable objects.  __ExitException could then
> derive from __SystemException and store the exit value.  But it is not
> clear how this would work for multithreaded programs, with which I
> have little experience in the context of C++ exceptions. Presumably,
> the __ExitException would have to be thrown in all threads and could
> interrupt functions that would otherwise not throw exceptions -- I
> can't say I understand all the implications.

It's more complicate than that. The base class of all throwable objects is 
Throwable. Error and Exception are derived from Throwable. Destructors, finally 
blocks, and scope statements are all skipped when a Throwable is thrown unless 
it is derived from Exception. So, there is no proper cleanup unless an 
Exception is thrown. Right now, the compiler, the runtime, and programmers can 
all assume that

try
{
    //code
    //1
}
catch(Exception e)
{
    //2
}

either #1 or #2 will be hit in that code if proper cleanup is occuring. In 
fact nothrow relies on this. If you wrap a function call in a try-catch block 
which catches Exception, then the function it's called in can be nothrow even 
if the function being called throws an exception. If we tried to have another 
exception type which was for exiting, then you'd  get this weird situation 
where nothrow functions _can_ throw when the program is being shutdown 
properly, and that could be a big problem.

Functions in D are set up around the idea that the only way to exit a function 
and have proper cleanup occur is to either return from it or have an Exception 
thrown from it. You're trying to have another way added. It's not that it's 
necessarily impossible, but it would likely require the redesign of several 
features and would break the assumptions made by a lot of code.

> For cleanup that needs to be done no matter what the exception, I
> would just use a finally{} block.

Yes and no. finally gets hit whether an Exception is thrown or the try block is 
exited normally, but it isn't run when a non-Exception Throwable (generally an 
Error) is thrown, so it gurantees nothing on unsafe shutdown. And if you were 
using exit, what would be the proper behavior? Neither the remainder of the 
try block nor the catch block would be run (since exit would skip the rest of 
the try block and skip the catch block entirely), which would likely break the 
assumptions made by a lot of code. It would certainly break scope. All of a 
sudden, you have something other than Error which won't hit scope(success) or 
scope(failure) but _will_ hit scope(exit), and that something is trying to be 
exiting _cleanly_ - unlike Error.

> UTILITY: Now, the actual utility of having a safe exit seems to be in
> question here. A common use of this is in OpenGL programs (that may be
> implicitly multithreaded) where the keyboard handler exit()s when I
> hit 'q' or ESC (which is quite common). Moreover, the underlying GUI
> framework or other APIs being used may conceivably have multiple
> threads and abstract this out for the user.  Is this an unreasonable
> use case for a safe exit? Or would this be too difficult to implement
> cleanly?

Exceptions only affect a single thread, so they're not going to help you 
terminate a multi-threaded program regardless. And to terminate another 
thread, you need a way to terminate it. The only ways to do that are to tell 
them to terminate themselves or to kill them. There is no way that I'm aware 
of built into threads to tell them that it's time to shutdown and then let 
them do it cleanly (which is what you'd need for a clean shutdown). You could 
use std.concurrency to inform them to shutdown or have a shared flag which 
indicates that it's time for all threads to shutdown, but you couldn't use 
pthreads or the Windows equivalent to tell a thread to shutdown cleanly. So, 
the only means generally available to terminate a thread is to forcibly kill 
it (as C's exit does), making automatic cleanup impossible.

_Some_ cleanup can be done when exit is called using atexit and on_exit, but 
the stack won't be unwound properly, so RAII, scope statements, and finally 
blocks aren't going to be run properly. So, critical, global stuff can be 
potentially cleaned up, but you can't get a fully clean shutdown without 
actually returning or having an Exception thrown from every function in every 
thread.

So, in general, the best way to handle taking down a multi-threaded 
application cleanly is to message each thread (be it via std.concurrency or a 
flag or whatever) which isn't going to shutdown on its own (e.g. after finishing 
some calculation) that it needs to shutdown, wait for all threads to shutdown, 
and then terminate the program by exiting main. Whether that's always possible 
when dealing with C libraries, I don't know, but I believe that that's really 
the only way to actually get a fully clean shutdown of a multi-threaded 
program in either C++ or D. In some cases, you may be able to cleanly shutdown 
most of the program and then be forced to use exit due to C stuff that you have 
no control over, but without at least shutting down the parts that you can 
cleanly, the D stuff in general isn't going to shut down cleanly.

Now, it could be that your D program will be just fine even if it isn't 
shutdown cleanly (e.g. it's not like you're going to get memory lost from your 
system or whatnot), but that depends on what your program is doing and what 
clean up gets skipped when exit is called. In general though, exiting by 
returning from main is by far the best way to exit a program and the only way 
that you can do so 100% cleanly.

- Jonathan M Davis


More information about the Digitalmars-d-learn mailing list