在Linux上创建PHP在线评分系统:execBehavior、进程ID和grep


Creating a PHP Online Grading System on Linux: exec Behavior, Process IDs, and grep

背景

我正在使用PHP和MySQL编写一个简单的在线评委(一个代码评分系统)。它采用C++和Java中提交的代码,对它们进行编译和测试。

这是在旧版本的Ubuntu上运行PHP5.2的Apache。

我目前正在做什么

我有一个php程序,它无限循环,通过调用另一个php程序

//for(infinity)
    exec("php -f grade.php");
//...

每隔十分之一秒。让我们把第一个称为looper.php,把第二个称为grade.php。(检查点:grade.php应该在"for"循环继续之前完全完成运行,对吗?)

grade.php从MySQL数据库中提取最早提交的需要评分的代码,将该代码放在一个文件(test.[cpp/java])中,并连续调用另外两个php程序,分别命名为compile.phptest.php,如下所示:

//...
exec("php -f compile.php");
//...
//for([all tests])
    exec("php -f test.php");
//...

(检查点:compile.php应该在调用test.php的"for"循环开始之前完全完成运行,对吗?)

compile.php然后编译test.[cpp/java]中的程序作为后台进程。现在,让我们假设它正在编译一个Java程序,并且test.java位于一个子目录中。我现在有

//...
//$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);
//...

在CCD_ 13中。它重定向javac的输出,因此javac应该作为后台进程运行。。。而且它似乎起作用了。$out应该在$out[0]中获取javac的进程id。

真正的问题

如果由于某种原因编译时间超过10秒,我希望停止编译;如果程序在10秒之前停止编译,我希望结束compile.php。由于我上面调用的exec("javac...是一个后台进程(或者它是?),如果不查看进程id,我就无法知道它何时完成,而进程id本应更早存储在$out中。紧接着,在compile.php中,我用一个10秒的循环调用exec("ps ax | grep [pid].*javac");,看看pid是否仍然存在:

//...
$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);
//...

这似乎不起作用。至少在某些时候。通常情况下,test.phpjavac停止之前就开始运行,导致test.php在尝试运行java程序时无法找到主类。我认为由于某种原因绕过了循环,尽管事实可能并非如此。在其他情况下,整个放坡系统会按预期工作。

同时,test.php在一定的时间限制内运行程序时也使用了相同的策略(使用X秒循环和grep),并且存在类似的错误。

我认为错误在于grep即使在javac仍在运行时也找不到javac的pid,导致10秒的循环提前中断。你能发现一个明显的bug吗?一个更谨慎的bug?我使用exec有问题吗?$out有问题吗?还是发生了完全不同的事情?

谢谢你阅读我的长问题。感谢所有的帮助。

我刚刚提出了这段代码,它将运行一个进程,如果进程运行时间超过$timeout秒,则终止进程。如果它在超时之前终止,它将在$output中具有程序输出,在$return_value中具有退出状态。

我已经测试过了,看起来效果很好。希望你能根据自己的需要进行调整。

<?php
$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
                fclose($pipes[1]);
                fclose($pipes[0]);
                proc_terminate($process); // terminate process
                $return_value = proc_close($process); // get return value
                $terminated   = true;
                break;
            }
        } else {
            // process finished before timeout
            $output = stream_get_contents($pipes[1]); // get output of command
            // close descriptors
            fclose($pipes[1]);
            fclose($pipes[0]);
            proc_close($process); // close process
            $return_value = $stat['exitcode']; // set exit code
            break;
        }
    }
    if (!$terminated) {
        echo $output;
    }
    echo "command returned $return_value'n";
    if ($terminated) echo "Process was terminated due to long execution'n";
} else {
    echo "Failed to start process!'n";
}

引用:proc_open()、proc_close()、proc_get_status()、pro_terminate()