Reputation: 47
So my program spawns a number of child processes in response to certain events, and I'm doing something ike this to keep track and kill them upon program exit (Perl syntax):
my %children = ();
# this will be called upon exit
sub kill_children {
kill 'INT' => keys %children;
exit;
}
# main code
while(1) {
...
my $child = fork();
if ($child > 0) {
$children{$child} = 1;
} elsif ($child == 0) {
# do child work ...
exit();
} else {
# handle the error
}
}
So the idea is as above. However, there's a blatant race condition there, in that a given child can start and terminate before the father has a chance to run and record its pid in the %children hash. So the father may end up thinking that a given pid belongs to an active child, even if this child has terminated.
Is there a way to do what I'm trying to accomplish in a safe way?
Edit: To better keep track of children, the code can be extended as follows (which however also suffer of the exact same race condition, so that's why I didn't write it fully in the first place):
my %children = ();
sub reap {
my $child;
while (($child = waitpid(-1, WNOHANG)) > 0) {
#print "collecting dead child $child\n";
delete $children{$child};
}
}
$SIG{CHLD} = \&reap;
# this will be called upon exit
sub kill_children {
local $SIG{CHLD} = 'IGNORE';
kill 'INT' => keys %children;
exit;
}
# main code
while(1) {
...
my $child = fork();
if ($child > 0) {
$children{$child} = 1;
} elsif ($child == 0) {
# do child work ...
exit();
} else {
# handle the error
}
}
Even in this case, the contents of %children may not reflect the actual active children.
Edit 2: I found this question, which is exactly about the same problem. I like the solution suggested in there.
Upvotes: 0
Views: 1959
Reputation: 43495
On UNIX it's not a race condition. This is the standard way to handle fork()
. When the child process exits, its status is changed to "terminated"; it becomes a zombie. It still has an entry in the process table until the parent process calls one of the wait
functions. Only after that is the dead process really removed.
Even if the parent sets itself up to ignore SIGCHLD, it still wouldn't qualify as a race condition; the parent would just have a PID that's not valid anymore. In that case, wait()
would return ECHILD. But setting SIGCHLD would free up a child's PID, possibly leading to the parent trying to kill a process that is not a child.
On Windows, which doesn't have a fork
call, it is emulated by creating a thread in the perl process. See perlfork. I'm not knowlegable enough about Windows to opinionate about if that could cause a race condition, but I suspect not.
Upvotes: 2