views:

51

answers:

2

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?

+6  A: 

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.

Chris Johnsen
+1 `waitpid` outside of a `SIGCHLD` handler is also effective at getting rid of zombies
mobrule
+9  A: 

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.

mobrule