Background
I am writing a simple online judge (a code grading system) using PHP and MySQL. It takes submitted codes in C++ and Java, compiles them, and tests them.
This is Apache running PHP 5.2 on an old version of Ubuntu.
What I am currently doing
I have a php program that loops infinitely, calling another php program by
//for(infinity)
exec("php -f grade.php");
//...
every tenth of a second. Let's call the first one looper.php
and the second one grade.php
. (Checkpoint: grade.php
should completely finish running before the "for" loop continues, correct?)
grade.php
pulls the earliest submitted code that needs to be graded from the MySQL database, puts that code in a file (test.[cpp/java]
), and calls 2 other php programs in succession, named compile.php
and test.php
, like so:
//...
exec("php -f compile.php");
//...
//for([all tests])
exec("php -f test.php");
//...
(Checkpoint: compile.php
should completely finish running before the "for" loop calling test.php
even starts, correct?)
compile.php
then compiles the program in test.[cpp/java]
as a background process. For now, let's assume that it's compiling a Java program and that test.java
is located in a subdirectory. I now have
//...
//$dir = "./sub/" or some other subdirectory; this may be an absolute path
$start_time = microtime(true); //to get elapsed compilation time later
exec("javac ".$dir."test.java -d ".$dir." 2> ".$dir
."compileError.txt 1> ".$dir."compileText.txt & echo $!", $out);
//...
in compile.php
. It's redirecting the output from javac
, so javac
should be running as a background process... and it seems like it works. The $out
should be grabbing the process id of javac
in $out[0]
.
The real problem
I want to stop compiling if for some reason compiling takes more than 10 seconds, and I want to end compile.php
if the program stops compiling before 10 seconds. Since the exec("javac...
I called above is a background process (or is it?), I have no way of knowing when it has completed without looking at the process id, which should have been stored in $out
earlier. Right after, in compile.php
, I do this with a 10 second loop calling exec("ps ax | grep [pid].*javac");
and seeing if the pid still exists:
//...
$pid = (int)$out[0];
$done_compile = false;
while((microtime(true) - $start_time < 10) && !$done_compile) {
usleep(20000); // only sleep 0.02 seconds between checks
unset($grep);
exec("ps ax | grep ".$pid.".*javac", $grep);
$found_process = false;
//loop through the results from grep
while(!$found_process && list(, $proc) = each($grep)) {
$boom = explode(" ", $proc);
$npid = (int)$boom[0];
if($npid == $pid)
$found_process = true;
}
$done_compile = !$found_process;
}
if(!done_compile)
exec("kill -9 ".$pid);
//...
... which doesn't seem to be working. At least some of the time. Often, what happens is test.php
starts running before the javac
even stops, resulting in test.php
not being able to find the main class when it tries to run the java program. I think that the loop is bypassed for some reason, though this may not be the case. At other times, the entire grading system works as intended.
Meanwhile, test.php
also uses the same strategy (with the X-second loop and the grep) in running a program in a certain time limit, and it has a similar bug.
I think the bug lies in the grep
not finding javac
's pid even when javac
is still running, resulting in the 10 second loop breaking early. Can you spot an obvious bug? A more discreet bug? Is there a problem with my usage of exec
? Is there a problem with $out
? Or is something entirely different happening?
Thank you for reading my long question. All help is appreciated.
See Question&Answers more detail:
os