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.
#!/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; } }
(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; }
In reply to Problems with flushing after fork() by smiffy
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |