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

I am trying to start an asynchronous process from a web application. The idea is that I fork(), then redirect to another page whilst the child process gets on with a lengthy job in the background (simulated by my sleep(20).)

Despite having tried a variety of ways of flushing STDOUT, the parent process always waits for the child to finish. Can anyone see what I'm doing wrong?

The $SIG{CHLD}='IGNORE' was supposed to stop zombie processes, but didn't - I always end up with a [asynctest.pl] <defunct> in my ps output. But zombies are the least of my worries when STDOUT doesn't want to flush.

SOLVED - thanks to ruzam and ikegami for the solution. The child process has to be detached using setsid(). I used Net::Server::Daemonize to simplify this. See Code Listing 2 for the relevant parts, including how I got the required UID and GID for daemonize() and cleaned up the PID file. (The Apache user is different on my development box and the test server - and possibly different again on the production server. This is why the UID/GID has to be considered an unknown.)

It should be noted that the "it's not possible" attitude in Perl FAQ 8 isn't exactly correct and that the system("foo.pl &") recipe produces exactly the same hang that was the problem in the first place.

Code Listing

#!/usr/bin/perl # This is asynctest.pl # Running under Apache 2.2/Linux. use strict; use warnings; use CGI; use IO::Handle; # Tried this: # STDOUT->autoflush(1); my $cgi=new CGI; unless ($cgi->param('a') eq 'foo') { my $pid=$cgi->param('pid'); print<<EOT; Content-type: text/html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head><title>foo</title></head> <body> <p>$pid</p> <form method="post" action="asynctest.pl"> <div><input type="hidden" name="a" value="foo" /></div> <div><input type="submit" value="press me" /></div> </form> </body></html> EOT exit; } else { # Tried this: $SIG{CHLD}='IGNORE'; if (my $pid=fork()) { # Redirect back to home screen, passing # the PID so we can keep an eye on it. print "Status: 302 Moved Temporarily\n"; print "Location: asynctest.pl?c=$pid\n\n"; # Tried this: # # my $old_fh=select(STDOUT); # $|=1; # select($old_fh); # And tried this: # # STDOUT->flush(); # And this: # # close STDOUT; # ...in varying combinations. # But this process always waits # for the sleep() in the other # process before sending the # redirect. exit; } else { close STDOUT; sleep(20); exit; } }

Code Listing 2

(use Net::Server::Daemonize is assumed.)

#snipped if (my $pid=fork()) { $|=; # Set auto-flush. print "Status: 302 Moved Temporarily\n"; print "Location: asynctest.pl?c=$pid\n\n"; close STDOUT; exit; } else { # We don't really need the PID file, but # daemonize() complains if you don't provide it, # despite the manpage saying it's optional. my $pidfile="/var/tmp/async$$.pid"; my @current_user=getpwuid($<); daemonize($current_user[2],$current_user[3],$pidfile); sleep(20); unlink($pidfile); # otherwise it doesn't go away exit; }

Replies are listed 'Best First'.
Re: Problems with flushing after fork()
by ikegami (Patriarch) on Feb 12, 2010 at 08:02 UTC
    Net::Server::Daemonize will redirect STD* to /dev/null (which is safer than closing them) and call setsid for you.

      Thanks. I was getting a wee bit concerned about closing STDERR!

Re: Problems with flushing after fork()
by ahmad (Hermit) on Feb 12, 2010 at 03:05 UTC

    You'll have to:

    close STDOUT; close STDIN; close STDERR;
    not just STDOUT in order to let it work

      Thanks. Whilst this lets the desired output return immediately, this is all that happens - the sleep() is no longer executed. No process exists after the output comes back.

        To complete the separation you must also dis-associate the forked child from the parent.
        use POSIX qw(setsid); ... # inside your forked child setsid() or die "Can't start a new session: $!";

        Well, I've just tried it now and it works

        #!/usr/bin/perl use strict; use warnings; use CGI; my $q = CGI->new; print $q->header; if (my $pid = fork ) { print "Hello, world - PID $pid\n"; exit; }else{ close STDOUT; close STDERR; close STDIN; sleep(20); exit; }

        I have verified that the script is still running: ps auxx | grep perl