I am trying to write a socket server that forks for every connection. I have been successful except for one small caveat: my child processes use Net:OpenSSH->capture2() which requires that $SIG{CHLD} not be set to IGNORE or to a custom signal handler. How can I reap my children without setting the signal handler or slowing down the parent process with wait or waitpid?
Here is my server code:
my $sock = new IO::Socket::INET (
LocalHost => 'localhost',
LocalPort => '1337',
Proto => 'tcp',
Listen => SOMAXCONN,
Reuse => 1,
);
die "Could not create socket: $!\n" unless $sock;
my $new_client, $pid;
while($new_client = $sock->accept()){
next if $pid = fork;
die "fork: $!" unless defined $pid;
close $sock;
while(<$new_client> ) {
#do Net::OpenSSH stuff
}
exit;
} continue {
close $new_client;
}
If I use the code as shown above, everything works but I end up with a bunch of zombie processes. If I add
local $SIG{CHLD} = 'IGNORE';
the zombies are reaped, but the Net::OpenSSH->capture2() method call has a messed up return code. I'm presuming my signal handler is interfering with some custom handler that Net::OpenSSH needs to work properly?
Go ahead and set up a SIGCHLD handler in the parent process, but disable it in the child processes -- for example put a local $SIG{CHLD}
immediately after the fork
call.
In the child processes, SIGCHLD events come from the Net::OpenSSH
methods, and the Net::OpenSSH
module will handle those events.
In the parent process, SIGCHLD events come from your child processes exiting. Those are exactly the events you are interested in and the ones you need to handle to prevent zombies.
If you never need to ignore children and use Net::OpenSSH in the same process then you might be able to use $SIG{CHLD} = 'IGNORE'
in the processes that do not use Net::OpenSSH (e.g. the single server process that forks off “auto-reaped” children) and reset it to $SIG{CHLD} = 'DEFAULT'
in the processes that use Net::OpenSSH (e.g. the children of the server).
Alternatively, you could use a non-blocking waitpid
in a loop at after each new client connection. You can still end up with one or more zombies hanging around, but they will all be reaped at the next connection. If you switch to using select
(or something like IO::Select), you could set an upper bound on the “lifetime” of the zombies by doing a select on your listening socket and doing a round of non-blocking zombie reaping after every timeout return as well as every “socket ready” return.
From the waitpid
section of perlfunc manpage:
If you say
use POSIX ":sys_wait_h"; #... do { $kid = waitpid(-1, WNOHANG); } while $kid > 0;
then you can do a non-blocking wait for all pending zombie processes. Non-blocking wait is available on machines supporting either the waitpid(2) or wait4(2) syscalls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With