# FILE: Submit.pm # AUTHOR: Timothy Bailey # CREATE DATE: 1/2002 # PROJECT: MCAST # DESCRIPTION: queue-based webserver package Submit; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(startJob cancelJob runProgram doCancel); use lib qw(@MODULE_DIR@); use MetaGlobals; use Process; use UID; use Log; use Queue; use CGI; use CGI::Carp qw(fatalsToBrowser); use CGIutil; use strict; no strict 'subs'; # let us use EAGAIN ##################################################################### # startJob ##################################################################### sub startJob { my ($query, $settings, $log, $uid, $run_pgm_ref) = @_; $log->debug("Starting the job $uid"); $log->log("Starting the job"); my $redirectto = "$SITE_URL/cgi-bin/nph-endpointtest.cgi"; my $runningModPerl = $ENV{MOD_PERL} ? 1 : 0; my $forkOK = forkOK(); # check that system will let us fork. setUpSpawn($forkOK); # register signal handlers, etc. # Set up a signal file which is used to let us know when the analysis # is ready. move to process.pm writeSemaphore($uid, "3 ", 1); # overwrite. (3=how many files are there. # todo: replace this with some more intelligent cue to how long the # job will take) # Important part. my $pid; if ($forkOK) { $log->debug("Not running mod_perl."); } FORK: if ($forkOK && ($pid = fork)) { # parent. $log->debug("In the parent process (redirecting to $redirectto?$UID_FIELD=$uid&PROCESS=$pid). Child is $pid"); writeSemaphore($uid, "$pid ", 0); print $query->redirect(-location=>"$redirectto?$UID_FIELD=$uid&PROCESS=$pid", -nph=>1); open (STDERR, ">/dev/null"); open (STDOUT, ">/dev/null"); wait(); # wait for the child to finish. not needed when $SIG{CHLD} = 'IGNORE'; } elsif ( (defined $pid || !$forkOK) && !$runningModPerl ) { my $parent = getppid(); if (!$forkOK) { print $query->redirect(-location=>"$redirectto?$UID_FIELD=$uid&PROCESS=$$", -nph=>1); } else { $log->debug("In the child process (processing). Parent is $parent, I am $$"); } open (STDERR, ">/dev/null"); open (STDOUT, ">/dev/null"); queueJob($query, $settings, $log, $uid, $run_pgm_ref); if ( $forkOK ) { $log->debug("Exiting child process"); exit(1); } } elsif ($runningModPerl) { $log->debug("Running mod_perl section"); require Apache; my $r = Apache->request; $r->post_connection(sub { queueJob($query, $settings, $log, $uid, $run_pgm_ref); }); writeSemaphore($uid, "$$ ", 0); $log->debug("post_connection registered, redirecting to $redirectto?$UID_FIELD=$uid&PROCESS=$$"); print $query->redirect(-location=>"$redirectto?$UID_FIELD=$uid&PROCESS=$$", -nph=>1); # under modperl, runs under a httpd process. $log->debug("Redirect done"); } elsif ($! == EAGAIN) { # should be recoverable. sleep(5) ; redo FORK; } else { error ("Could not fork: $!", $query); } } # sub startJob ##################################################################### # queueJob # # Not exported. ##################################################################### sub queueJob { my ($query, $settings, $log, $uid, $run_pgm_ref) = @_; my $i; my $command; # enter the job into the queue. Execution halts here until &enqueue # returns, which could take up to $PATIENCE. $log->debug("Queueing job $uid"); my $go = enqueue($uid, $log); $log->debug("Job queued"); writeSemaphore($uid, "${go}|", 0); # wait around until it is done. if ($go =~ /ABORT/) { dequeue($uid, $log); error("Failed to enqueue: $go", $query, $log); } elsif ($go =~ /RUN/) { my $runlog = "$LOG_DIR/${uid}.runlog"; $log->debug("run job"); my $status = &$run_pgm_ref($settings, $uid, $runlog, $log); # all the action happens here. $log->debug("job over $status"); if ($status != 0) { dequeue($uid, $log); writeSemaphore($uid, 'ABORT', 0); error("run_pgm_ref returned an error ($status).", $query, $log); return; } } else { dequeue($uid, $log); error("Failed to enqueue: INVALID RESPONSE: $go", $query, $log); return; } # signal that we are done. dequeue($uid, $log); $log->debug("Processing is done for $uid"); writeSemaphore($uid, 'DONE', 0); } #queueJob ############################################################### # runProgram: reasonably generic. Send a command to the system, # saving stdout in outfile and logging stderr. ############################################################### sub runProgram { my ($outfile, $errfile, $log, @args) = @_; # Clobber zero-length output file, to allow existence check in next step. if (-z $outfile) { unlink($outfile); $log->debug("Clobbered zero length file $outfile"); } # If the file exists, don't bother. if (-e $outfile) { $log->log("$outfile already exists."); return 1; } my $cmdstr = join(" ", @args); $log->debug("Running $cmdstr"); $log->debug("Saving output to $outfile"); $log->debug("Errors to $errfile"); # store stderr in the runlog for the user. The monitor will # periodically copy this into the regular log. my @start = times(); # make sure we can write to the error file. eval { system("/bin/touch $errfile"); # FIXME: Use $TOUCH. }; if ($@) { $log->log("Could not write to the errlog $errfile"); return 1; } # The spawning process set the CHLD signal handler to 'IGNORE' # Set it locally to 'DEFAULT'. Otherwise system always returns -1 local $SIG{CHLD} = 'DEFAULT'; my $status = system("$cmdstr 2>> $errfile 1> $outfile"); # log the outcome. my @end = times(); my $elapsed = ($end[0] - $start[0]) + ($end[1] - $start[1]) + ($end[2] - $start[2]) + ($end[3] - $start[3]); my $CPUtime += $elapsed; my $runstring = sprintf("Elapsed time = %.2f CPU seconds.", $elapsed); $log->debug($runstring); chmod(0664, $outfile); return $status >> 8; # 16 bit status word; high byte is the exit status. } #runProgram ##################################################################### # check to see if we are here because of a previous job that has been # cancelled, and make sure the job gets killed. Redirect the user to # the form if need be. ##################################################################### sub doCancel { my ($query, $uid, $log, $redirectto) = @_; my $cancel_me = validateParam($query, 'CANCEL', 0, $SITE_MANAGER); if ($cancel_me) { $log->log("Cancelling job for $uid ($cancel_me received)."); $log->debug("Cancelling job for $uid ($cancel_me received)."); my $killed = cancelJob($cancel_me, $uid); dequeue($uid, $log); $log->log("killed $killed"); $log->debug("killed $killed redirecting to $redirectto"); print $query->redirect(-location=>"$redirectto", -nph=>1); $log->debug("Bye"); $log->log("Bye"); exit(1); } } #doCancel