tags:

views:

594

answers:

3

Hi there. I'm having trouble forking a long-running process from some code running under mod_perl2.

Everything works for the most part, but it seems that the forked process is holding open handles to Apache's logfiles - this means Apache won't restart while the process is running (I get a 'failed to open logfiles' message).

Here's the code I'm using:

use POSIX; # required for setsid

# Do not wait for child processes to complete
$SIG{CHLD} = 'IGNORE';

# fork (and make sure we did!)
defined (my $kid = fork) or die "Cannot fork: $!\n";

if ($kid) {
 return (1, $kid);
}else {
 # chdir to /, stops the process from preventing an unmount
 chdir '/' or die "Can't chdir to /: $!";

 # dump our STDIN and STDOUT handles
 open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
 open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";

 # redirect for logging
 open STDERR, '>', $log_filename or die "Can't write to log: $!";

 # Prevent locking to apache process
 setsid or die "Can't start a new session: $!";

 # execute the command
 exec( $cmd, @args );

 die "Failed to exec";
}

Back in the mod_perl1 days, I recall using $r->cleanup_for_exec to solve this problem, but it doesn't seem to be supported under mod_perl2. (Edit: Apparently it's not required any more..)

Any advice on how to correctly start a long-running process from mod_perl2 without these problems would be greatly appreciated!

+1  A: 

Try closing your STDIN/STDOUT handles before the fork.

nezroy
That'd completely break Apache.. The parent process still has to produce a response (and send it via STDOUT) for the client..
Dan
The parent process could send its response before forking; unless you're actually doing more work in the parent after the fork. If the log files are not on the STDIN/OUT/ERR descriptors, you could just start closing any open descriptors >2 you find in the child process.
nezroy
Yeah problem is that apache process goes on to serve other requests when it's finished this one, killing it's STDERR breaks its logging, and I suspect killing its STDIN stops it from communicating with the parent proc. Anyway - I tried and it doesn't fix the problem ;)
Dan
Try just looping from 3 to 1024 in the child, doing 'open($fh, "< close($fh);', where # is the idx of the loop. Catch and ignore errors. Ideally this should close any remaining open descriptors. Maybe not a permanent fix but will potentially verify that open descriptors are the problem.
nezroy
Also, way back in the Perl daemonizing day, it used to be prudent to fork twice. I don't remember why, just that it helped prevent orphaned processes. Worth a shot :)
nezroy
Mm.. "fork twice" may not be all that clear. It would be one fork, and then the child forks again, and the child of the 2nd fork is the process to keep. The child of the first fork would then exit.
nezroy
Yes, you have to fork twice, once before the setsid (to create the new process), and another time after the setsid (to make sure your session and process group don't have a leading process).
Leon Timmermans
A: 

In my (formerly mod_perl, now FCGI) code, I have in the "else" clause of the "if ($kpid)",

    close STDIN;
    close STDOUT;
    close STDERR;
    setsid();

Also, for reasons that I forgot, I immediately fork again, and then in that child re-open STDIN, STDOUT, and STDERR.

So it looks like:

$SIG{CHLD} = 'IGNORE';

# This should flush stdout.
my $ofh = select(STDOUT);$| = 1;select $ofh;

my $kpid = fork;
if ($kpid)
{
    # Parent process
    waitpid($kpid, 0);
}
else
{
    close STDIN;
    close STDOUT;
    close STDERR;
    setsid();
    my $gpid = fork;
    if (!$gpid)
    {
        open(STDIN, "</dev/null") ;#or print DEBUG2 "can't redirect stdin\n";
        open(STDOUT, ">/dev/null") ;#or print DEBUG2 "can't redirect stdout\n";
        open(STDERR, ">/dev/null") ;#or print DEBUG2 "can't redirect stderr\n";
        # Child process
        exec($pgm, @execargs) ;# or print DEBUG2 "exec failed\n";
    }
    exit 0;
}
Paul Tomblin
+2  A: 

You probably want to read this discussion. It seems you shouldn't fork on mod_perl unless you know how to prepare things. You have to use a module such as Apache2::SubProcess

Leon Timmermans