Discussion:
Pthreads and SIGCHLD
(too old to reply)
Chris Vine
2003-09-10 11:07:36 UTC
Permalink
Hi,

A program I have relies on the parent thread which launches a new process
with fork() receiving (ie having its thread of execution asynchronously
interrupted by) the SIGBCHLD signal handler when the child terminates,
rather than that signal handler running in another thread's thread of
execution.

This is because the signal handler relies on certain variables not being
changed (except by itself) while it is executing. This can be guaranteed
if the signal handler executes in the thread of execution of the thread
which launched it, because (as the program is written) that is the only
thread which uses those variables. Of course if another thread can "pick
up" the signal and execute the signal handler, then that guarantee no
longer holds, because then the parent thread and its SIGCHLD handler could
be running concurrently. To deal with that I would have to put mutexes in
the signal handler.

Does POSIX/pthreads say anything about this, or is it implementation/OS
dependent? And does it make any difference if a new thread is a launched
by the parent process after the parent process starts a new process with
fork() (before the child process terminates)?

Chris.
--
To reply by e-mail, remove the "--nospam--" in the address
Sean Burke
2003-09-10 16:54:26 UTC
Permalink
Post by Chris Vine
Hi,
A program I have relies on the parent thread which launches a new process
with fork() receiving (ie having its thread of execution asynchronously
interrupted by) the SIGBCHLD signal handler when the child terminates,
rather than that signal handler running in another thread's thread of
execution.
This is because the signal handler relies on certain variables not being
changed (except by itself) while it is executing. This can be guaranteed
if the signal handler executes in the thread of execution of the thread
which launched it, because (as the program is written) that is the only
thread which uses those variables. Of course if another thread can "pick
up" the signal and execute the signal handler, then that guarantee no
longer holds, because then the parent thread and its SIGCHLD handler could
be running concurrently. To deal with that I would have to put mutexes in
the signal handler.
Does POSIX/pthreads say anything about this, or is it implementation/OS
dependent? And does it make any difference if a new thread is a launched
by the parent process after the parent process starts a new process with
fork() (before the child process terminates)?
Chris.
Is there any reason that the parent thread could not
call sigwait from time to time to pick up these signals?
That way you don't need to mess with async signal handlers.

-SEan
David Butenhof
2003-09-10 17:31:13 UTC
Permalink
Post by Chris Vine
A program I have relies on the parent thread which launches a new process
with fork() receiving (ie having its thread of execution asynchronously
interrupted by) the SIGBCHLD signal handler when the child terminates,
rather than that signal handler running in another thread's thread of
execution.
This is because the signal handler relies on certain variables not being
changed (except by itself) while it is executing. This can be guaranteed
if the signal handler executes in the thread of execution of the thread
which launched it, because (as the program is written) that is the only
thread which uses those variables. Of course if another thread can "pick
up" the signal and execute the signal handler, then that guarantee no
longer holds, because then the parent thread and its SIGCHLD handler could
be running concurrently. To deal with that I would have to put mutexes in
the signal handler.
First, you can't use mutexes in a signal handler. So that's not an option.
Post by Chris Vine
Does POSIX/pthreads say anything about this, or is it implementation/OS
dependent? And does it make any difference if a new thread is a launched
by the parent process after the parent process starts a new process with
fork() (before the child process terminates)?
Um, both. There have been some broken implementations. However, in POSIX the
SIGCHLD signal is "process directed", which means it will be delivered to
any arbitrary thread that does not have SIGCHLD blocked at the time it
arrives; and if all threads have it blocked it will be held pending against
the PROCESS until such time as ANY thread unblocks the signal.

When a thread is created it inherits the current signal mask of the creating
thread. That is, if thread T1 has SIGCHLD masked, and called
pthread_create() to create thread T2, then T2 will begin with SIGCHLD
masked as well -- even if T1 immediately UNMASKS SIGCHLD. (The inheritance
is static, not dynamic.)

So one option, if you're writing the main program, would be to block SIGCHLD
in main(), before creating any threads, so that ALL threads begin with
SIGCHLD blocked. And then unblock SIGCHLD only in that one thread that'll
fork() a child. This won't work if more than one thread might need to
execute your fork() code, or if any other code might possibly unblock
SIGCHLD for any reason.
--
/--------------------[ ***@hp.com ]--------------------\
| Hewlett-Packard Company Tru64 UNIX & VMS Thread Architect |
| My book: http://www.awl.com/cseng/titles/0-201-63392-2/ |
\----[ http://homepage.mac.com/dbutenhof/Threads/Threads.html ]---/
Chris Vine
2003-09-10 20:05:54 UTC
Permalink
David Butenhof wrote:

[snip]
Post by David Butenhof
So one option, if you're writing the main program, would be to block
SIGCHLD in main(), before creating any threads, so that ALL threads begin
with SIGCHLD blocked. And then unblock SIGCHLD only in that one thread
that'll fork() a child. This won't work if more than one thread might need
to execute your fork() code, or if any other code might possibly unblock
SIGCHLD for any reason.
Hmm. This is problematic. In my case, I think the easiest course is for
the new thread to block SIGCHLD as soon as it starts, and for the main
thread which creates the new thread to lock a mutex just before it launches
the thread to stop any action involving the critical variables within the
synchronous main thread in the instant between the new thread being created
and the new thread unlocking the mutex once it has set up its signal mask.
The main thread is in effect locking against itself until the new thread
releases it, on the off chance that in the instant between the new thread
starting and it setting up its own signal mask the new thread might pick up
the signal and execute the handler before it has set itself up not to do
so. What the signal handler does isn't important so long as it doesn't
fiddle with the critical variables concurrently with the main thread. If I
cannot put mutexes in the signal handler, I can at least control what the
main synchronous thread is doing to the critical variables.

This should work if, as I understand it correctly, the signal mask set up by
the new thread will not affect the signal mask of the thread which created
it.

Incidentally, why can't you use a mutex in a signal handler, aside from
causing mayhem if the signal is locked for too long (and what is too long)?

On a linked point, if the main thread which does a fork() then wait()s on
its child, could it fail if the child also does a wait() - in other words
could a wait on all processes (not a waitpid()) by the new thread cause the
new thread to pick up the status of the terminating process and cause the
main thread to wait for ever? (This isn't an issue for my program, but I
like to store these things in the memory bank).

I've heard people say you shouldn't mix processes with threads, and now I
understand why, but in this case it is unavoidable because after fork()ing
the new process does an exec().

Chris.
--
To reply by e-mail, remove the "--nospam--" in the address
David Butenhof
2003-09-11 12:02:41 UTC
Permalink
Post by Chris Vine
[snip]
Post by David Butenhof
So one option, if you're writing the main program, would be to block
SIGCHLD in main(), before creating any threads, so that ALL threads begin
with SIGCHLD blocked. And then unblock SIGCHLD only in that one thread
that'll fork() a child. This won't work if more than one thread might
need to execute your fork() code, or if any other code might possibly
unblock SIGCHLD for any reason.
Hmm. This is problematic. In my case, I think the easiest course is for
the new thread to block SIGCHLD as soon as it starts, and for the main
thread which creates the new thread to lock a mutex just before it
launches the thread to stop any action involving the critical variables
within the synchronous main thread in the instant between the new thread
being created and the new thread unlocking the mutex once it has set up
its signal mask.
You can do that with a semaphore, but not with a mutex. Mutexes have
ownership, and you cannot lock a mutex in one thread and unlock it in
another. Semaphores don't have ownership, so you can sem_wait in one thread
and sem_post in another. (You can even sem_post in a signal handler.)
Post by Chris Vine
This should work if, as I understand it correctly, the signal mask set up
by the new thread will not affect the signal mask of the thread which
created it.
Each thread's signal mask (and pending mask) are completely independent.
Only the signal ACTIONS are shared among all threads in the process. (So
you cannot set SIGCHLD to do one thing in one thread and something else in
another thread.)
Post by Chris Vine
Incidentally, why can't you use a mutex in a signal handler, aside from
causing mayhem if the signal is locked for too long (and what is too long)?
Lots of reasons. Logically it's a really bad idea because it's too easy to
accidentally code nasty deadlocks. A signal might arrive, for example, in a
thread that already holds the mutex. More practically, though, making mutex
operations "async signal safe" would mean blocking signals in many
implementations, adding much more overhead than the cost of the mutex
operation itself.
Post by Chris Vine
On a linked point, if the main thread which does a fork() then wait()s on
its child, could it fail if the child also does a wait() - in other words
could a wait on all processes (not a waitpid()) by the new thread cause
the new thread to pick up the status of the terminating process and cause
the main thread to wait for ever? (This isn't an issue for my program,
but I like to store these things in the memory bank).
Process parentage is by process, not thread. So yes, any thread in a given
process that does any open-ended "wait for any child" can accept
termination of any child created by any thread in the process. This is
precisely why there's no "pthread_joinall" as has frequently been
requested: it'd be a mess without a thread-based ownership hierarchy. (And
such a hierarchy would be expensive and complicated and logically a bad
idea in the first place.)
Post by Chris Vine
I've heard people say you shouldn't mix processes with threads, and now I
understand why, but in this case it is unavoidable because after fork()ing
the new process does an exec().
The semantics of fork() are pretty squirrelly, and the model just doesn't
work with intra-process concurrency. Sun's "forkall()" tried to address a
few issues at one end of the problem spectrum, and pthread_atfork()
addresses a few at the other end, but neither is anywhere near perfect and
there's a broad range of problems neither touches.

The posix_spawn() function is far better than fork(), but isn't widely
available and maybe never will be -- and is even less likely to be widely
used because fork()/exec*() ARE everywhere, and well known despite the
mess. (And even worse, because posix_spawn() was designed to encapsulate
most of the odd things people do between fork and exec* in the child, it's
a rather complicated function.)
--
/--------------------[ ***@hp.com ]--------------------\
| Hewlett-Packard Company Tru64 UNIX & VMS Thread Architect |
| My book: http://www.awl.com/cseng/titles/0-201-63392-2/ |
\----[ http://homepage.mac.com/dbutenhof/Threads/Threads.html ]---/
Chris Vine
2003-09-11 20:57:09 UTC
Permalink
David Butenhof wrote:

[snip]

Thanks. That was very informative and useful.

Chris
--
To reply by e-mail, remove the "--nospam--" in the address
Loading...