Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Wait, Select, and a SIGCHLD Race Condition

253 views
Skip to first unread message

stu...@cs.rochester.edu

unread,
Dec 11, 1987, 12:43:40 AM12/11/87
to
I need advice (or sympathy) for handling a race condition in 4.3BSD
flavored UNIX. Briefly, I want to use wait3 to reap all the dead or
stopped children of a process, then use select to wait for the first
new IO or child activity. Sketch something like this:

while (0 < (pid = wait3(..., WNOHANG, ...))) {
/* do something with child */
}

/* XXX Race condition is here */

numfds = select(...);
if (numfds < 0) {
if (errno == EINTR)
/* caught a signal, what kind was it, etc */
}

There is a race condition between reaping children and starting the
select. It is possible that a child can change status, a SIGCHLD gets
delivered *before* I enter select, I don't notice it, enter select and
hang forever. Even if I have a handler for SIGCHLD that sets a flag
and I check that flag immediately before calling select, there is still
a (small) window of vulnerability.

Ideally, I would like to set the signal mask to block SIGCHLD and have
select release the signal *after* starting to wait. That would allow
me to ensure that *all* dead children are noticed. However, select
does not release any signals as far as I can tell. Berkeley truly
improved the signal handling features going to 4.3, but the (improved)
features don't seem to let me write this code safely. (In particular,
the sigblock, signal, sigpause, signal, setsigmask idiom is of no help
here.)

I would appreciate advice on how to safely avoid this race condition
given 4.3BSD features. I suspect that it's not possible, but would be
delighted to learn otherwise (see next paragraph for an equivocation
for "not possible"). It's not essential that the skeleton code look
like that given above; all that's needed is that I/O and child
activity is processed as soon as *either* is available. Neither kind
of activity is guaranteed to happen, and some events may already have
happened, which must not be ignored.

There *is* a kludge that I can fall back on, but I would really like to
avoid it: Put a maximum on the timeout given to select and check for
more children when select times out. Even if I miss a SIGCHLD, I would
still reap the child. This is doable, but a pain, because I am
managing timer requests in addition to IO and child requests in the
same package; keeping the real timeouts straight from the kluge
timeouts (which might coincide!) is real ugly. The whole point of this
package is to multiplex lots of request and AVOID POLLING. The kludge
is, of course, nothing but polling.

Stu Friedberg {ames,cmcl2,rutgers}!rochester!stuart stu...@cs.rochester.edu

Keeper of the Sacred Tablets

unread,
Dec 11, 1987, 12:01:30 PM12/11/87
to
Suppose you set all your fd's of interest to be asynchronous and you
handle the SIGIO instead of doing select? Then it's signal vs.
signals instead of signals vs. select. A blocking select would
translate into a sigpause().
________________________________________________________
Matt University ma...@oddjob.uchicago.edu
Crawford of Chicago {astrovax,ihnp4}!oddjob!matt

Richard Draves

unread,
Dec 11, 1987, 1:00:57 PM12/11/87
to

One solution (this idea is from GNU Emacs) is to put a large timeout
on the select. Make a SIGCHLD handler that clears the timeout.
Then if the handler is called before entering select, the select won't
block.

Rich

Wen-King Su

unread,
Dec 11, 1987, 1:58:57 PM12/11/87
to
In article <51...@sol.ARPA> stu...@cs.rochester.edu writes:
>I need advice (or sympathy) for handling a race condition in 4.3BSD
<flavored UNIX. Briefly, I want to use wait3 to reap all the dead or
>stopped children of a process, then use select to wait for the first
<new IO or child activity. Sketch something like this:

This method uses the timeout in a non-polled way. The ORIGINAL_TIMER
is the timer you are using. The ZERO_TIMER is a structure with zero
in all its fields. Now, instead of passing &ORIGINAL_TIMER to select,
you put its address in a pointer and pass that pointer instead so you
can change the pointer on the fly whenever a child signal is received.

on SIGCHLD interrupt, set time_ptr = &ZERO_TIMER;

do { time_ptr = &ORIGINAL_TIMER;
if (0 > (pid = wait3(..., WNOHANG, ...))) break;
.... }

numfds = select(...,time_ptr);
if(time_ptr == &ZERO_TIMER) { go back to wait for childs }
if (numfds < 0) { if (errno == EINTR) { ... } else { ... } }

/*------------------------------------------------------------------------*\
| Wen-King Su wen-...@vlsi.caltech.edu Caltech Corp of Cosmic Engineers |
\*------------------------------------------------------------------------*/

Keeper of the Sacred Tablets

unread,
Dec 11, 1987, 3:48:48 PM12/11/87
to
>Newsgroups: comp.unix.wizards
>Organization: Up against the wall of SCIENCE

Richard Draves

unread,
Dec 11, 1987, 6:48:33 PM12/11/87
to
> on SIGCHLD interrupt, set time_ptr = &ZERO_TIMER;
...
> numfds = select(...,time_ptr);
...

>| Wen-King Su wen-...@vlsi.caltech.edu Caltech Corp of Cosmic Engineers |

This method doesn't quite work. The problem is if the signal comes in after
the select procedure call but before the trap to the kernel, then the
select will end up blocking because the kernel will get ORIGINAL_TIMER
instead of ZERO_TIMER as the argument to the trap. The signal handler
must atomically zero out the actual timer structure.

Rich

Keeper of the Sacred Tablets

unread,
Dec 11, 1987, 11:09:12 PM12/11/87
to

Matt Costello

unread,
Dec 12, 1987, 12:53:50 AM12/12/87
to
In article <51...@sol.ARPA> stu...@cs.rochester.edu writes:
>I need advice (or sympathy) for handling a race condition in 4.3BSD
>flavored UNIX. Briefly, I want to use wait3 to reap all the dead or
>stopped children of a process, then use select to wait for the first
>new IO or child activity.

I've two methods I use to get around the race conditions in signals.
They are:

1. If you are not using SIGALRM for something else, have your timeout
routine re-enable the SIGALRM on 1 second intervals until it is
turned off in the outer level code. If the original signal hits
the timing hole then the second (or third) won't.
The beauty of this is that it usable in any version of UNIX, since
it uses no features specific to BSD or USG.

For wanting to not miss any child processes with SIGCHLD:

onedied() {
signal(SIGCHLD,SIG_DFL); /* will infinite loop otherwise */
signal(SIGALRM,onedied); alarm(1);
}

signal(SIGCHLD,onedied);
/* race condition is here... */
numfds = select(); /* or read(), or msgrcv() */
alarm(0);


2. For select() or any operation where the process is waiting on incoming
IO, you can have the signal routine send a dummy message that will
cause the select() to return immediately. Rather than aborting the
operation find some way to make it terminate normally. This works
wonderfully for SYSV message queues since it is perfectly legal to
send a zero length message.
--
Matt Costello <matt.c...@SanDiego.NCR.COM>
+1 619 485 2926 <matt.costello%SanDiego...@Relay.CS.NET>
{sdcsvax,cbosgd,pyramid,nosc.ARPA}!ncr-sd!matt

Keeper of the Sacred Tablets

unread,
Dec 12, 1987, 5:06:57 AM12/12/87
to

Keeper of the Sacred Tablets

unread,
Dec 12, 1987, 10:46:51 AM12/12/87
to

Wen-King Su

unread,
Dec 12, 1987, 10:59:15 AM12/12/87
to

OOPS, you are right. How about this:

on SIGCHLD interrupt, set time_struct = ZERO_TIMER;
...
numfds = select(...,&time_struct);

It should cover all possibilities. I knew there is a way, I just
couldn't remember all the details.

/*------------------------------------------------------------------------*\


| Wen-King Su wen-...@vlsi.caltech.edu Caltech Corp of Cosmic Engineers |

\*------------------------------------------------------------------------*/

Keeper of the Sacred Tablets

unread,
Dec 12, 1987, 6:03:02 PM12/12/87
to

Keeper of the Sacred Tablets

unread,
Dec 12, 1987, 9:45:06 PM12/12/87
to

Keeper of the Sacred Tablets

unread,
Dec 12, 1987, 11:55:18 PM12/12/87
to

Keeper of the Sacred Tablets

unread,
Dec 13, 1987, 4:56:27 AM12/13/87
to

Keeper of the Sacred Tablets

unread,
Dec 13, 1987, 12:20:53 PM12/13/87
to

James D. Allen

unread,
Dec 13, 1987, 6:45:32 PM12/13/87
to
In article <51...@sol.ARPA>, stu...@cs.rochester.edu writes:
> I need advice (or sympathy) for handling a race condition in 4.3BSD
> flavored UNIX. Briefly, I want to use wait3 to reap all the dead or
> stopped children of a process, then use select to wait for the first
> new IO or child activity. Sketch something like this:
>
> while (0 < (pid = wait3(..., WNOHANG, ...))) {
> /* do something with child */
> }
> /* XXX Race condition is here */
> numfds = select(...);
et cetera

This race condition is intriguing because it arises from "instruction
atomization" in the application program, and NOT due to limitations in the
Unix system interface. Two correct solutions have been posted which take
advantage of a Unix feature (FASYNC in one case, select timeout in the
other). It's worth noting, however, that the application code can detect
and fix the race without OS assistance, as shown in the crude fragment:

sig_catcher()
{
caught_signal = TRUE;
if (possible_race)
longjmp(jmp_buffer, 1);
}
wait_for_event()
{
...
if (setjmp(jmp_buffer)) {
signalled:
possible_race = FALSE;
/* process the signal */
caught_signal = FALSE;
} else {
possible_race = TRUE;
if (caught_signal)
goto signalled;
numfds = select(...);
/*
* No longer need to check EINTR!!
* if (numfds < 0 && errno == EINTR)
* goto signalled;
*/
possible_race = FALSE;
/* process the select */
}
...
}

I don't know how to do this without `longjmp' (or something even uglier).

>
> Stu Friedberg {ames,cmcl2,rutgers}!rochester!stuart stu...@cs.rochester.edu

James Allen {ucbvax,hplabs,seismo}!sun!betelgeuse!jamesa

Keeper of the Sacred Tablets

unread,
Dec 13, 1987, 6:50:53 PM12/13/87
to

Keeper of the Sacred Tablets

unread,
Dec 14, 1987, 12:32:21 AM12/14/87
to

Chris Torek

unread,
Dec 14, 1987, 2:30:10 PM12/14/87
to
In article <36...@sun.uucp> jamesa%betel...@Sun.COM (James D. Allen) writes:
>This race condition is intriguing because it arises from "instruction
>atomization" in the application program, and NOT due to limitations in the
>Unix system interface.

All user-level race conditions result from this; it can be (but does
not have to be) considered a deficiency in the interface. This very
problem is precisely why I want a signal mask in select's arguments:
so that none of the three kludges are necessary. (Some of you may
recall my recent diatribe on the subject.)

In summary:

FASYNC
select timeout
longjmp

The first method works only if you are willing to recode everything
to use signals (and you still need select!---but with immediate
timeout); the select timeout works but is somewhat tricky to code;
the longjmp works but only if you have exactly one jump. I would
probably use the select timeout trick myself. Here it is in its
full glory:

struct select_goo {
int *nfds; /* points to select's nfds argument */
struct timeval *timeout; /* ... to timeout argument */
struct select_goo *inner; /* previous select, if recursing */
} *select_goo;

catch_sigchld()
{

child_changed = 1;
if (select_goo) {
*select_goo->nfds = 0; /* saves work in select() */
timerclear(select_goo->timeout);
}
}

...
int nfds, omask, cc;
fd_set rfd, wfd, xfd;
struct timeval to;
struct select_goo goo;

/* copy these since select or SIGCHLD will modify them */
nfds = Nfds, rfd = Rfd, wfd = Wfd, xfd = Xfd, to = Timeout;

/* hold the signal while we build the goo */
omask = sigblock(sigmask(SIGCHLD));
if (child_changed) {
/* a signal already occurred, so skip all this */
(void) sigsetmask(omask);
return (0);
}
goo.nfds = &nfds;
goo.timeout = &to;
goo.inner = select_goo;
select_goo = &goo;

/* now safe to release the signal */
(void) sigsetmask(omask);

cc = select(nfds, &rfd, &wfd, &xfd, &to);

/* either the select selected, or the signal hit,
or the timeout expired */

/* undo the goo */
omask = sigblock(sigmask(SIGCHLD));
select_goo = goo.inner;

/* figure out which event occurred */
if (child_changed) {
/* the signal hit */
} else if (cc == 0) {
/* the timeout expired */
} else {
/* the select selected */
}
(void) sigsetmask(omask);
--
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
Domain: ch...@mimsy.umd.edu Path: uunet!mimsy!chris

Andrew Klossner

unread,
Dec 14, 1987, 4:24:32 PM12/14/87
to
[]

"Suppose you set all your fd's of interest to be asynchronous
and you handle the SIGIO instead of doing select? Then it's
signal vs. signals instead of signals vs. select. A blocking
select would translate into a sigpause()."

From the fcntl(2) man page (4.2BSD vintage):

"The asynchronous I/O facilities of FNDELAY and FASYNC are
currently available only for tty operations."

-=- Andrew Klossner (decvax!tektronix!tekecs!andrew) [UUCP]
(andrew%tekecs....@relay.cs.net) [ARPA]

robert fair

unread,
Dec 16, 1987, 9:05:56 AM12/16/87
to

Well, so far over 15 copies of this posting have reached us here at Bellcore.
Is anyone else suffering from the Xerox effect ?

Rob Fair
ihnp4!pyuxww!pyuxf!robf2

Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler
Polite News Filler

Bob Lenk

unread,
Dec 17, 1987, 5:12:18 PM12/17/87
to
> In summary:
>
> FASYNC
> select timeout
> longjmp

A fourth (not necessarily better) alternative is to have an extra pipe
or socket that the signal handler writes to, and select on it for
reading. One reply alluded to this general type of mechanism, but
didn't tie it into specific BSD features.

One warning about the longjmp() method is that it can be troblesome in
the presence of other signals. If SIGCHLD happens to interrupt another
signal handler, that handler can be aborted. This can be avoided by
setting the SIGCHLD bit in sv_mask for all other handlers that might be
invoked, further complicating things.

Bob Lenk
{ihnp4, hplabs}!hpfcla!rml

0 new messages