http://qs321.pair.com?node_id=211463

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

Brothers and Sisters, please consider this CGI script:

#doing stuff up here. my $pid; if (!defined ($pid = fork)){ DieNice("Unable to fork: $!\n"); }elsif (! $pid){ close(STDIN); close(STDOUT); close(STDERR); }else{ print header, start_html, h2('Sending Mail...'), "\t<meta http-equiv=\"refresh\" content=\"5; url=http://mydomain +.com:8090\">\n", end_html; #Here the user is redirected. } while (<@to>){ chomp($_); push @chunk, $_; $count ++; if ($count == 80){ #splits bcc into small chuncks $to = join ",", @chunk; mailout(); $count = 0; @chunk = (); } } #still doing more time consuming stuff...

I want the user to be redirected and freed from the CGI while it continues to run its code. Is this the correct way to do it? I borrowed this code from a usenet article but I don't fully understand it. Can someone please explain how this works and if it's correct?

Thank you,

Neil Watson
watson-wilson.ca

Replies are listed 'Best First'.
Re: Understanding fork
by RMGir (Prior) on Nov 08, 2002 at 16:44 UTC
    Seems fine to me.

    Here's what's happening. When fork is called, it either fails or succeeds. If it fails, it returns undef, so you log an error and DieNice.

    If it succeeds, it returns _twice_. Well, in reality, it spawns a separate process with its own copy of all your variables, file descriptors, etc, and both process run in parallel.

    In the parent process, fork() returns the process ID of the new child. So in that process, you redirect the user, and exit. (At least, I THINK you should exit, but you're falling out of that else clause in your sample, and doing the while twice...).

    The child process gets 0 back from fork. That process closes STDIN, STDOUT, and STDERR to detach itself from the parent process, and continues on to do the normal processing. Closing those 3 filehandles is not enough to completely detach in all situations. However, since you're being run by the web server, you don't have to worry about things like terminals. If you have any other files open before the fork, you should probably close them, as well.

    You should consider doing an open of STDERR to a log file after the close(STDERR), in case your script dies/warns...
    --
    Mike

    Edit: Was missing an important NOT in the "enough to completely detach" sentence...

      In the parent process, fork() returns the process ID of the new child. So in that process, you redirect the user, and exit. (At least, I THINK you should exit, but you're falling out of that else clause in your sample, and doing the while twice...).

      How do you prevent that?

      Neil Watson
      watson-wilson.ca

        Where you have the comment about redirecting the user, add
        $SIG{CHLD}='IGNORE'; exit;
        That way, the parent process will exit immediately, hopefully without waiting for the child process.
        --
        Mike
Re: Understanding fork
by robartes (Priest) on Nov 08, 2002 at 19:20 UTC
    The code is correct, but confusing with all the 'if not's. See RMGir's reply above for an explanation of what the code does. However, to make it less confusing, here's a fork()ing framework that uses straight ifs that are easier to understand (at least for me):
    use strict; if (defined(my $pid=fork()) { if ($pid) { # $pid is not 0, so this is the parent # Let's redirect the user (more extensive code in your example). redirect_user(); } else { # $pid=0, so this is the child my $retval=do_child_code(); exit $retval; } } else { # Oops, fork() returned undef - something is definitely wrong. DieNice("Unable to fork: $!\n"); } sub do_child_code { # Do your time consuming stuff, setting $retval to # a useful number return $retval; }
    You could do without the else block calling do_child_code, and have the child just fall through down to the child code, but this way it's a lot easier to understand and maintain.

    CU
    Robartes-

Re: Understanding fork
by ehdonhon (Curate) on Nov 08, 2002 at 20:38 UTC
    You probably also want to have your child do a $SIG{HUP} = 'IGNORE' But, there's an even better way. I suggest you take a look at Proc::Daemon.
Re: Understanding fork
by petral (Curate) on Nov 08, 2002 at 23:01 UTC
    You can finish the parent with   wait;   or waitpid $pid;   before exiting, then you won't leave "zombies" around.     See perlipc, for far more than you ever wanted to know.

      p