asohn has asked for the wisdom of the Perl Monks concerning the following question:

In the documentation of fork it says "Note that if your forked child inherits system file descriptors like STDIN and STDOUT...". Any elaboration on "like" would be appreciated - what specifically is inherited when you fork() a CGI script?

I'm trying to implement a non-blocking fork of a CGI script using this as a reference. However, I want to maintain/reopen the STDERR location in the child process but everything that I am trying also maintains the association with the parent process causing the parent to wait for the child to finish (e.g. the browser "loading" animation never stops). If I close STDERR or point it to /dev/null after the fork() it works fine but I need the child process to print STDERR messages to the same file that the parent process is using but still sever all associations with the parent process. So the way I'm implementing it is basically that upon submission the parent process will display a friendly message on the web page after the fork() and the child process will run for however long it needs to and kick off an email when it's done.

Script Workflow(-ish):
  page.cgi displays a form
  user submits the form
  page.cgi handles form submission
  print STDERR "forking...\n";
  fork()
  parent:
    page.cgi display "thanks, we'll email you when your request has been completed"
    end of execution and user goes off to different parts of the site.
  child:
    sleep 20; #page.cgi does a bunch of stuff based on the user's criteria
    print STDERR "sending email...\n"; #email the user with a summary of what actions were carried out by the script
    end of execution

So what's happening is although I'm receiving an email the "sending email..." isn't being logged which would mean that if something goes wrong in the child process our logs wouldn't reflect it. Turns out I was receiving a "Permission denied" error when trying to reopen STDERR for the child process but didn't know it until I changed the open(STDERR,...); to email me if it failed open(STDERR,...) || do {...$!...$mail->send()};
I don't know why I would be getting a "Permission denied" error if the script is being executed by www-data which opens STDERR in the first place (before the fork). It may have something to do with the way something is configured but I was curious if a monk had a more elegant solution.

Here's a bit of the code that implements the workflow that I described.
In the code you see below, the fork() is wrapped in a condition and only executes if the processing of the user's request is going to take a while to finish based on a threshold defined at the top of the script.

use POSIX qw(setsid);
use strict;

my $iteration_limit = 1; #15;

display_form();
process_form() if $cgi->param('action');

sub display_form {
    ...
}

sub process_form {
    my ($action) = @_;
    ...
    my $iterations = 0;
    map {map {$iterations++} ... #calculate iterations
    my $did_fork = 0;
    if ($iterations > $iteration_limit) {
        print STDERR "forking...\n";

        #non-blocking fork
        #$SIG{CHLD} = "IGNORE";
        #open(STDERR2, ">&STDERR")  || die "Cannot duplicate stderr: $!\n";
        defined(my $pid = fork())  || die "Cannot fork: $!\n";
        return if $pid;            #return if we are the parent process
        (setsid() != -1)           || die "Cannot start a new session: $!\n";
        chdir("/")                 || die "Cannot chdir to /: $!\n";
        open(STDIN,  "</dev/null") || die "Cannot read /dev/null: $!\n";
        open(STDOUT, ">/dev/null") || die "Cannot write to /dev/null: $!\n";
        close(STDERR);
        open(STDERR, ">>/var/log/apache/sitename-err.log") || do {
            my $err = $!;
            my $mail = ...::Email->new(
                'Type'    => 'Text/html',
                'To'      => 'my email address',#$email,
                'From'    => "site's email address",
                'Subject' => 'EMAIL SUBJECT (CHANGE THIS LATER) err',
                'Data'    => $err
            );
            $mail->send();
        };
        #close(STDERR);
        #open(STDERR, ">>&STDERR2") || die "Cannot restore stderr: $!\n";
        #close(STDERR2);
        #open(STDERR, ">&STDOUT")   || die "Cannot duplicate stdout: $!\n";
        #open(STDERR, ">&STDERR")   || die "Cannot duplicate stderr: $!\n";
        $did_fork++;
    }
    sleep 30; #simulate form processing

    #actual form processing commented out
    #...

    return unless $did_fork;
    print STDERR "sending email...\n";
    $renderer->{'template'} =~ s/\.html$/_email.html/;

    my $mail = ...::Email->new(
        'Type'    => 'Text/html',
        'To'      => 'my email address',#$email,
        'From'    => "site's email address",
        'Subject' => 'EMAIL SUBJECT (CHANGE THIS LATER)',
        'Data'    => ...
    );
    $mail->send();
    exit(0);
}

Replies are listed 'Best First'.
Re: CGI fork() inherits
by andal (Hermit) on Feb 14, 2012 at 09:20 UTC

    Effectively, fork inherits everything from it's parent, even memory. But it gets "copy" of everything. If the child performs "exec", then new process inherits only file descriptors that have FD_CLOEXEC flag cleared. Usually, this is used by the Web server that starts CGI scripts. The server establishes stdout and stdin of the forked child to use created by the Web server pipes and then does "exec" that will run CGI script.

    Looks like, your Web server waits till the CGI script closes both STDOUT and STDERR. Which means, that actually all of the data sent to STDERR goes first to Web server and then Web server forwards it to appropriate log file. Most likely, your script does not have enough privileges to open that file. The simplest solution is for your forked child to use different file.