背景
我正在使用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.php
和test.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.php
在javac
停止之前就开始运行,导致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()