2012-02-20 04:38



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

    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 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." -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
    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;

    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.

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答


  • doufen1890 doufen1890 9年前

    I just came up with this code that will run a process, and terminate it if it runs longer than $timeout seconds. If it terminates before the timeout, it will have the program output in $output and the exit status in $return_value.

    I have tested it and it seems to work well. Hopefully you can adapt it to your needs.

    $command = 'echo Hello; sleep 30'; // the command to execute
    $timeout = 5; // terminate process if it goes longer than this time in seconds
    $cwd = '/tmp';  // working directory of executing process
    $env = null;    // environment variables to set, null to use same as PHP
    $descriptorspec = array(
            0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
            1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
            2 => array("file", "/tmp/error-output.txt", "a") // stderr is a file to write to
    // start the process
    $process    = proc_open($command, $descriptorspec, $pipes, $cwd, $env);
    $startTime  = time();
    $terminated = false;
    $output     = '';
    if (is_resource($process)) {
        // process was started
        // $pipes now looks like this:
        // 0 => writeable handle connected to child stdin
        // 1 => readable handle connected to child stdout
        // Any error output will be appended to /tmp/error-output.txt
        // loop infinitely until timeout, or process finishes
        for(;;) {
            usleep(100000); // dont consume too many resources
            $stat = proc_get_status($process); // get info on process
            if ($stat['running']) { // still running
                if (time() - $startTime > $timeout) { // check for timeout
                    // close descriptors
                    proc_terminate($process); // terminate process
                    $return_value = proc_close($process); // get return value
                    $terminated   = true;
            } else {
                // process finished before timeout
                $output = stream_get_contents($pipes[1]); // get output of command
                // close descriptors
                proc_close($process); // close process
                $return_value = $stat['exitcode']; // set exit code
        if (!$terminated) {
            echo $output;
        echo "command returned $return_value
        if ($terminated) echo "Process was terminated due to long execution
    } else {
        echo "Failed to start process!

    References: proc_open(), proc_close(), proc_get_status(), proc_terminate()

    点赞 3 评论 复制链接分享