[gclist] Finalization and death notices

Jerrold Leichter jerrold.leichter@smarts.com
Wed, 10 Oct 2001 12:28:20 -0400 (EDT)


| Consider the following potential bug. The user builds a locker object which
| locks a given resource in its constructor and unlocks it in its destructor.
| The idea is to build a locker on entry to a control block and automatically
| unlock it on exit. Thus exceptions etc. cannot bypass the unlock.
| 
| All this seems very good and is surly an improvement over normal coding
| practice. But
| 
| { 
|    locker x(foo.lock);
| 
|    ...
|    ...
|    if (fork())
|      ...
|    else
|      ...
| }
| 
| How do we implement locker objects with current C++ destructors? What about
| our death notice & near death notice. Assume its important to unlock
| promptly.

The Posix thread definitions actually already deal with this issue.  First
of all, in a multi-threaded program, fork() clones *only* the calling thread.
The new process will have only one thread, and it will be executing in the
"if" clause.  Second, Posix provides a "pthread_atfork()" call that lets you
install (nominally, any number of) fork handlers.  Fork handlers come in
three "styles", depending on when they get called:  In the parent, just before
the fork is actually done; in the parent, just before the fork() call returns;
and in the child, just before the fork() call returns.  The theory is that
you are supposed to release resources like mutexes in your fork handler.
Exactly how and when depends on things like whether your mutexes are process
private or process shared.

Since fork handlers don't obey scope, and cannot be canceled once set, if
you really wanted something that worked with locker objects, you'd have to
build it, using a fork handler effectively as a signalling mechanism.

If you don't unlock your process-shared mutexes *before* the fork, then in
fact you have two processes that each might think they "own" the mutex.  If
your fork() will shortly be followed by an exec(), it really doesn't much
matter:  Shutting down a process that happens to hold even a process shared
lock won't unlock it, so the child won't interfere with anything the parent is
doing with its locks.  Also, of course, exec doesn't execute destructors.

If, on the other hand, you really want the new process to synchronize with the
old, you've got a lot of work to do.  (In particular, you'd better make sure
exactly one of them unlocks the mutex - which will require a bit of work if the
locker object does it automatically.  An obvious quick fix is for the locker
to record the process id in its constructor.  In the destructor, only unlock
the mutex if the PID is still the same.)

Anything you do in this vein is almost certainly beyond the scope of the Posix
standards, however, since the following restriction applies:  "If the calling
process is multi-threaded, the child process may only execute async-signal
safe functions until one of the exec functions is called."  The async-signal
safe functions are also those that can be invoked from a signal handler -
typically a very limited set.  None of the pthread routines are normally
async-signal safe - so you can't actually lock or unlock anything in the new
process much less create a new thread, until you've exec'd.

							-- Jerry