chanklaus has asked for the wisdom of the Perl Monks concerning the following question:
Dear fellow monks,
the problem I have is already discussed in several places in the internet. However, although I read web pages for hours and tried at least half a dozen suggestions I couldn't manage to get it solved.
The problem is this: I have a CGI script, from which a time-consuming job is started. I don't want to let the user wait for minues, being insecure whether the job has crashed or is running. So when the request to the CGI script is started, I fork the process and let the child create a web page which is regularly refreshed, displaying the elapsed time, while the parent runs its long task. The parent finally creates a file, and this is the signal for the self-refreshing "waiting" HTML page created by the child to switch to a "results" HTML page. In principle this already works fine - only that the child doesn't die after the initial creation of the "waiting" HTML page: it's hanging, until the parent has finished its job and died, and THEN is refreshing - and immediately jumping to the "results" HTML page.
The problem, I guess, is obvious: I didn't make the child entirely independent from the parent, so that the web server still waits for data to arrive until the parent has died. But I found no means to break this dependency. Who can help me?
Because I think that this way it will be easier to track the problem, and my little demo program might be of interest for others with the same intention I'll post the entire little demo program (demo.cgi) here, although it's pretty long for a posting. I hope I don't strain anybodys patience.
Many thanks for your help!
Best greetings and wishes,
Yours
chanklaus
Here's the code for the small demo program. It should work the way I post it here - except for my problem. The only thing still to be changed by you before you can use the program is to adapt the absolute path defined in '$TmpAbs', which I had to symbolize through 'WEBSERVER'. The long job run in the parent is mimicked here by a "sleeping" time lastling for as many seconds as there are "Ticks" defined in the input form of the start page.
#!/usr/bin/perl -w
require 5.004;
use strict;
my (@Query,$CGIPath,$TmpAbs,$TmpVrt,$HtmOut,$HtmTmp);
my ($HtmName,$Status,$ForkVal,$STime,$Elapsed,$Ticks,$Prg,$QI);
$Prg = "demo.cgi";
@Query = split(/&/,$ENV{'QUERY_STRING'});
$CGIPath = $ENV{'SCRIPT_NAME'};
while (chop($CGIPath) ne "/") { }
$TmpAbs = "/www/WEBSERVER/htdocs/tmp";
$TmpVrt = "/tmp";
$HtmName = "demo.html";
$HtmOut = sprintf("%s/%s",$TmpAbs,$HtmName);
$HtmTmp = sprintf("%s/%s.tmp",$TmpAbs,$HtmName);
for ($QI=0;$QI<=$#Query;++$QI) {
if ($Query[$QI] =~ /^Time=/) { ($STime) = ($Query[$QI] =~ /Time=(.*?
+)$/); }
elsif ($Query[$QI] =~ /^Tick=/) { ($Ticks) = ($Query[$QI] =~ /Tick=(
+.*?)$/); }
elsif ($Query[$QI] =~ /^STAT=/) { ($Status) = ($Query[$QI] =~ /STAT=
+(.*?)$/); } }
if (!defined($Status)) { $Status = 0; }
#---- If "Start Page" is selected ----------
if ($Status == 0) { &StartPage; }
#---- If "Processing" is selected ----------
elsif ($Status == 1) {
if (!open(HTMFILE,">$HtmTmp")) { &ErrorMsg(1,$HtmTmp); }
$ForkVal = fork();
if (!defined($ForkVal)) { &ErrorMsg(0,""); }
if ($ForkVal == 0) {
$Elapsed = "00:00:00";
&WaitingPage(sprintf("%s/%s?STAT=2&Time=%s",$CGIPath,$Prg,&CurrTim
+e));
close(STDOUT); close(STDERR); close(STDIN);
exit(0);
}
else {
print HTMFILE &ProcessPage;
close(HTMFILE);
system("mv $HtmTmp $HtmOut");
}
}
#---- If "Waiting Page" is selected ----------
elsif ($Status == 2) {
$Elapsed = &ElapsedTime;
if (-s $HtmOut) { &WaitingPage(sprintf("%s/%s",$TmpVrt,$HtmName)); }
else { &WaitingPage(sprintf("%s/%s?STAT=2&Time=%s",$CGIPath,$Prg,$ST
+ime)); }
exit(0);
}
sub ErrorMsg {
my ($Stat,$Mess) = @_;
print "Content-type: text/html \n\n";
print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN
+\">\n\n<html>\n";
print "<head>\n <title>DEMO Error Message</title>\n</head>\n\n";
print "<body>\n<center>\n\n";
print "<span style=\"font-size:22pt; color:Green\">DEMO ERROR MESSAG
+E</span><br>\n<br>\n";
if ($Stat == 0) { print "!!! System error - could NOT fork process !
+!!<br>\n"; }
elsif ($Stat == 1) { print "!!! System error - could NOT write \"HTM
+L\" file '$Mess'!!!<br>\n"; }
print "</center>\n</body>\n</html>\n";
exit(0);
}
sub StartPage {
print "Content-type: text/html \n\n";
print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN
+\">\n<html>\n";
print "<head>\n <title>DEMO START PAGE</title>\n</head>\n\n";
print "<body>\n<center>\n<br>\n";
print "<span style=\"font-size:22pt; color:Green\">DEMO START PAGE</
+span><br>\n<br>\n";
print "<form action=\"$CGIPath/demo.cgi\" method=\"get\">\n";
print "<input type=\"hidden\" name=\"STAT\" value=\"1\">\n";
print "<b>TYPE A "TICK NUMBER":</b><br>\n";
print "<input type=\"text\" name=\"Tick\" size=\"10\"><br><br>\n";
print "<input type=\"submit\" value=\"PROCESS\"> <input type=\"reset
+\" value=\"RESET FORM\">\n";
print "</form>\n</center>\n</body>\n</html>\n";
}
sub ProcessPage {
my $Html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitiona
+l//EN\">\n<html>\n";
$Html .= "<head>\n <title>DEMO PROCESS PAGE</title>\n";
$Html .= " <meta HTTP-EQUIV=\"Expires\" CONTENT=\"NOW\">\n</head>\n
+\n";
$Html .= "<body>\n<center>\n<br>\n";
$Html .= "<span style=\"font-size:22pt; color:Green\">DEMO PROCESS P
+AGE</span><br>\n<br>\n";
$Html .= "<span style=\"font-size:22pt; color:Violet\">...THAT'S THE
+ TEST...</span><br>\n<br>\n";
$Html .= "...<a href=\"$CGIPath/demo.cgi\">go back to the DEMO start
+ page</a>...<br>\n";
$Html .= "</center>\n</body>\n</html>\n";
sleep($Ticks);
$Html;
}
sub WaitingPage {
my ($Url) = @_;
print "Content-type: text/html \n\n";
print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN
+\">\n<html>\n";
print "<head>\n <title>...DEMO is running...</title>\n";
print " <META HTTP-EQUIV=\"refresh\" content=\"2; url=$Url\">\n</he
+ad>\n\n";
print "<body>\n<center>\n<br>\n";
print "<span style=\"font-size:22pt; color:Red\">...DEMO is running.
+..</span><br>\n";
print "<br>\n<span style=\"font-size:22pt\">Don't go BACK!</span><br
+>\n<br>\n";
print "<span style=\"font-size:20pt; color:Blue\">Elapsed Time: $Ela
+psed</span><br>\n";
print "</center>\n</body>\n</html>\n";
}
sub CurrTime {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime
+(time);
my $LocTime = "$mday $mon $year $wday $yday $isdst";
$LocTime = sprintf("%02d:%02d:%02d",$hour,$min,$sec);
$LocTime;
}
sub ElapsedTime {
my ($Sec,$Min,$Hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime
+(time);
my $CTime = "$mday $mon $year $wday $yday $isdst";
$CTime = ((($Hour * 60) + $Min) * 60) + $Sec;
($Hour,$Min,$Sec) = split(/:/,$STime);
my $ITime = ((($Hour * 60) + $Min) * 60) + $Sec;
if ($CTime < $ITime) {
$CTime += 86400; }
my $ETime = $CTime - $ITime;
$Sec = $ETime % 60;
$ETime = ($ETime - $Sec) / 60;
$Min = $ETime % 60;
$Hour = ($ETime - $Min) / 60;
$ETime = sprintf("%02d:%02d:%02d",$Hour,$Min,$Sec);
$ETime;
}
Remark: Normally I comment (almost) every code line, and I did it as well here, but I couldn't find out how it's done without creating a mess in the posting. However, if someone would like to have the commented code I'd gladly send it to her/him.
Learning is like swimming against the current - as soon as you stop you'll drift back
(Chinese proverb)
Re: PERL CGI - waiting page is hanging
by davidov0009 (Scribe) on Nov 23, 2007 at 14:35 UTC
|
This probably won't help to solve your problem completely, but it would certainly help to shorten up your program considerably and make it more robust and easier to read. You should use the CGI module from CPAN intead of printing all your own headers and html etc.
http://search.cpan.org/dist/CGI.pm/CGI.pm
| [reply] |
|
|
I would go further and say that if roll-your-own CGI isn't causing problems for him yet, it most certainly will in the future.
Just use CGI. Do it.
In fact, "while (chop($CGIPath) ne "/") { }" may very well be the problem. It's better to use path_info() to do ... whatever that while loop is meant to do.
| [reply] [d/l] [select] |
Re: PERL CGI - waiting page is hanging
by scorpio17 (Canon) on Nov 23, 2007 at 15:46 UTC
|
You might consider doing this with AJAX. Once the job request is made, the web page will display a "working..." message (while the job is run server-side, independent of the page view). Once the job completes, you'll see a "Done!" message.
I wrote a demo to illustrate this a while back in answer to another question, but it may help you as well.
Good luck!
| [reply] |
Re: PERL CGI - waiting page is hanging
by realflash (Acolyte) on Jan 03, 2010 at 19:57 UTC
|
I have cracked this. The clue is here. Although it is referring to something slightly different, the principal is the same: because the child created in the fork inherits the STDIN and STDOUT connected to the web browser, the web browser connection is held open. The parent is exiting properly, and the child is taking over its role, making it seem to the user like nothing has changed. To solve this, you can reopen STDIN and STDOUT to another destination (in my case /dev/null). Obviously you will need to be sure that you are done outputting things to the web browser. The code above is now:
#!/usr/bin/perl -wT
use strict;
use CGI ':standard';
die "Could not fork()\n" unless defined (my $kidpid = fork);
if ($kidpid) {
print header;
print "done\n";
print STDERR "Parent exited\n";
exit;
}
else
{
open STDOUT, '>/dev/null' or die "Can't open /dev/null: $!";
open STDIN, '</dev/null' or die "Can't open /dev/null: $!";
sleep 20;
print STDERR "Child exited \n";
}
which works as you'd want it to. In conjunction with some AJAX you finally get the task maangement you want.
If you want to have a go at storing and restoring STDOUT for later, you can try some examples here. | [reply] [d/l] |
|
|
| [reply] |
|
|
Thank you thank you thank you! I've been searching for hours for the solution to this!!
| [reply] |
|
|
That was a glorious end to my struggle to find why the request goes on for infinite spins when my script forks.
That's awesome.
--Saumadip
| [reply] |
Re: PERL CGI - waiting page is hanging
by realflash (Acolyte) on Jan 03, 2010 at 19:22 UTC
|
#!/usr/bin/perl -wT
use strict;
use CGI ':standard';
die "Could not fork()\n" unless defined (my $kidpid = fork);
if ($kidpid) {
print header;
print "done\n";
print STDERR "Parent exited\n";
exit;
}
else
{
sleep 20;
print STDERR "Child exited \n";
}
If you run this CGI at a command prompt, it does what you would expect - the parent exits and you get the command prompt again. 20s later, the child exits and you see the output broadcast in the terminal.
Now run it as a CGI, and although you see "done" printed straight away in the browser, the page still appears to be loading until the 20s is up (the server appears to still be holding the connection open). A clue I found at http://coding.derkeiler.com/Archive/Perl/perl.beginners/2003-11/1136.html implies this could be to do with Apache. I found this issue running with mod_cgid in Apache 2.2.9 on Linux kernel 2.6.27.
My application is AJAX and is experiencing this problem; AJAX cannot solve it directly. My AJAX call cannot determine if the task started successfully, because it does not get the result until it ends. I am going to see if I can work around it by making the call and ignoring the response; hopefully the FF JS engine is multithreaded enough to let me do that.
I would love to hear from anyone who has managed to make Apache let perl fork properly in this scenario. Having said all that, if you read http://www.xav.com/perl/lib/Pod/perlfork.html, it says "the parent and every pseudo-child created by it that is also a pseudo-parent will only exit after their pseudo-children have exited.", so I'm not sure the command prompt example should work as it does. | [reply] [d/l] |