Abandoning a Process (original) (raw)
ISO/IEC JTC1 SC22 WG21 N2440 = 07-0310 - 2007-10-05
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
This document is a revision of N2383= 07-0243 - 2007-08-05.
Introduction
The primary problem with destruction of static-duration objects is access to static-duration objects after their destructors have executed, thus resulting in undefined behavior. To prevent this problem,N2382 Dynamic Initialization and Destruction with Concurrencyrequires that all user threads finish before destruction begins (via a call to exit
or return from main
).
In many applications, finishing a thread requires canceling the thread from the outside. SeeN2320 Multi-threading Library for Standard C++and its successors for proposed mechanisms to canceling threads. For various reasons, this cancellation needs to be cooperative.
Problem
In essence, the problem is that applications may not be able to cooperatively cancel. There are a number of reasons for this problem.
- Cooperative cancellation requires that all code be able to handle cancellation. This requirement is new; nearly all existing code is not properly written. The difficulty of the code is equivalent to exception safety, which is known to be a hard task.
- Synchronization dependences between modules may require threads to cancel in a particular order. However, for large-scale applications, no one knows the full set of dependences between modules because there are many and they are constantly changing. Furthermore, there may even be circular dependences, making a cancellation order unachievable.
- Operating system constraints seem to indicate that programs blocked on I/O may not be cancelable and hence cannot participate in cooperative cancellation.
In summary, relying on only cooperative cancellation to terminate processes will not meet the needs of many applications.
Solution
If cooperative cancellation is not possible, it would be better to never execute static destructors. The Committee has clearly stated that it wishes to preserve execution of static destructors in normal applications. So, we need a mechanism to abandon an application process without cooperatively canceling all threads and without executing the static destructors.
Such a mechanism already exists in the _Exit
function. Unfortunately, that function has no mechanism to enable applications to flush critical information to stable storage. In short, _Exit
is too drastic.
The existing standard has a facility (atexit
) to register functions that must execute when the program exits (via a return from main
or an explicit call to exit
). That function, though, implies execution of the static destructors, and thus does not solve the problem.
The solution lies between _Exit
and exit
, a function that executes registered functions, but does not execute static destructors. Threads may continue to run while registered functions execute, and static-duration variables are still live for both the threads and the registered functions.
We considered using the existing atexit
function registration facility, but ultimately decided against it because programs often use atexit
for destructor-like actions, and because existing compilers often use atexit
to register static destructors. Thus a new registration facility is needed.
The burden of converting to the new registration facility is not large, programmers need simply find all calls to atexit
and, after verifying that the register function is appropriate, also call the new registration facility. The number of calls to atexit
is not large.
For the new facility, we have chosen quick_exit
and at_quick_exit
. Other logical choices, like _Exit
already seem to be in use, while there are no occurances of quick_exit
in a Google code search.
Changes to the Standard
Only one section of the standard need change to support this proposal.
18.4 Start and termination [lib.support.start.term]
In paragraph 1, table 20, edit
Functions:
abort atexit at_quick_exitexit quick_exit
After the existing final paragraph 9, add a new function entry:
extern "C" { int at_quick_exit(void (*f)(void)); } extern "C++" { int at_quick_exit(void (*f)(void)); }
Add a new paragraph 10,
_Effects:_The
at_quick_exit()
functions register the function pointed to byf
, to be called without arguments whenquick_exit
is called. [ _Note:_Theat_quick_exit
registrations are distinct from theatexit
registrations, and applications may need to call both registration functions with the same argument. — end note ] Theat_quick_exit()
functions are thread-safe.
Add a new paragraph 11,
For the execution of a function registered with
at_quick_exit()
, if control leaves the function because it provides no handler for a thrown exception,terminate()
is called (18.7.3.3).
Add a new paragraph 12,
_Implementation limits:_The implementation shall support the registration of at least 32 functions.
Add a new paragraph 13,
_Returns:_The
at_quick_exit()
function returns zero if the registration succeeds, nozero if it fails.
After the new paragraph 13, add a new function entry:
quick_exit(int status)
Add a new paragraph 14,
_Effects:_Functions registered by calling
at_quick_exit
are called. Objects are not destroyed as a result of callingquick_exit()
. Functions withat_quick_exit
registration are called in the reverse order of their registration, except that a function is called after any previously registered functions that had already been called at the time it was registered.
Add a new paragraph 15,
After calling registered functions,
at_quick_exit
will call_Exit
with the givenstatus
. [ _Note:_The standard file buffers are not flushed. See ISO C subclause 7.20.4.4. — end note ]
Add a new paragraph 16,
_Returns:_The
quick_exit()
function never returns to its caller.