How do I fork properly with mod_perl2?
Asked Answered
A

3

9

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!

Albatross answered 23/1, 2009 at 2:31 Comment(1)
Hi Dan, how did you solve this issue, I am facing the same problem. Earlier I was using mod_perl1 and $r->cleanup_for_exec was working fine but in mod_perl2 this is not longer required, so could you please help me to implement this in mod_perl2? Thanks in advance.Ilianailine
S
3

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

Sotted answered 23/1, 2009 at 3:16 Comment(0)
R
1

Try closing your STDIN/STDOUT handles before the fork.

Roye answered 23/1, 2009 at 2:38 Comment(7)
That'd completely break Apache.. The parent process still has to produce a response (and send it via STDOUT) for the client..Albatross
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.Roye
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 ;)Albatross
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.Roye
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 :)Roye
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.Roye
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).Sotted
H
1

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;
}
Harrold answered 23/1, 2009 at 3:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.