--- spamd/spamd.raw (revision 1329511) +++ spamd/spamd.raw (working copy) @@ -589,6 +589,7 @@ my $timeout_child; # processing timeout (headers->finish), 0=no timeout my $clients_per_child; # number of clients each child should process my %children; # current children +my @children_exited; if ( defined $opt{'max-children'} ) { $childlimit = $opt{'max-children'}; @@ -1033,6 +1034,8 @@ # child_handler() if !$scaling || am_running_on_windows(); child_handler(); # it doesn't hurt to call child_handler unconditionally + child_cleaner(); + do_sighup_restart() if defined $got_sighup; for (my $i = keys %children; $i < $childlimit; $i++) { @@ -2534,13 +2537,24 @@ # my $pid = waitpid(-1, WNOHANG); last if !$pid || $pid == -1; - my $child_stat = $?; + push(@children_exited, [$pid, $?, $sig, time]); + } - if (!defined $children{$pid}) { - # ignore this child; we didn't realise we'd forked it. bug 4237 - next; - } + $SIG{CHLD} = \&child_handler; # reset as necessary, should be at end +} +# takes care of dead children, as noted by a child_handler() +# called in a main program flow (not from a signal handler) +# +sub child_cleaner { + while (@children_exited) { + my $tuple = shift(@children_exited); + next if !$tuple; # just in case + my($pid, $child_stat, $sig, $timestamp) = @$tuple; + + # ignore this child if we didn't realise we'd forked it. bug 4237 + next if !defined $children{$pid}; + # remove them from our child listing delete $children{$pid}; @@ -2550,15 +2564,10 @@ my $sock = $backchannel->get_socket_for_child($pid); if ($sock) { $sock->close(); } } - - unless ($Mail::SpamAssassin::Logger::LOG_SA{INHIBIT_LOGGING_IN_SIGCHLD_HANDLER}) { - info("spamd: handled cleanup of child pid [%s]%s: %s", - $pid, (defined $sig ? " due to SIG$sig" : ""), - exit_status_str($child_stat,0)); - } + info("spamd: handled cleanup of child pid [%s]%s: %s", + $pid, (defined $sig ? " due to SIG$sig" : ""), + exit_status_str($child_stat,0)); } - - $SIG{CHLD} = \&child_handler; # reset as necessary, should be at end } sub restart_handler {