在 Linux 上创建 PHP 在线评分系统:exec 行为、进程 ID 和 grep [英] Creating a PHP Online Grading System on Linux: exec Behavior, Process IDs, and grep
问题描述
背景
我正在使用 PHP 和 MySQL 编写一个简单的在线判断器(代码分级系统).它采用 C++ 和 Java 提交的代码,对其进行编译和测试.
这是在旧版 Ubuntu 上运行 PHP 5.2 的 Apache.
我现在在做什么
我有一个无限循环的php程序,通过
调用另一个php程序//for(无穷大)exec("php -f grade.php");//...
每十分之一秒.让我们调用第一个 looper.php
和第二个 grade.php
.(检查点:grade.php
应该在for"循环继续之前完成运行,对吗?)
grade.php
从 MySQL 数据库中提取最早提交的需要评分的代码,并将该代码放入文件 (test.[cpp/java]
),并依次调用另外2个php程序,分别命名为compile.php
和test.php
,如下:
//...exec("php -f compile.php");//...//for([所有测试])exec("php -f test.php");//...
(检查点:compile.php
应该在调用 test.php
的for"循环开始之前完全运行,对吗?)
compile.php
然后编译test.[cpp/java]
作为后台进程中的程序.现在,让我们假设它正在编译一个 Java 程序并且 test.java
位于一个子目录中.我现在有
//...//$dir = "./sub/" 或其他一些子目录;这可能是绝对路径$start_time = microtime(true);//稍后获取经过的编译时间exec("javac ".$dir."test.java -d ".$dir." 2> ".$dir."compileError.txt 1> ".$dir."compileText.txt & echo $!", $out);//...
在 compile.php
中.它正在重定向 javac
的输出,所以 javac
应该作为后台进程运行......而且它似乎可以工作.$out
应该在 $out[0]
中获取 javac
的进程 ID.
真正的问题
如果由于某种原因编译需要超过 10 秒,我想停止编译,如果程序在 10 秒前停止编译,我想结束 compile.php
.由于 exec("javac...
我在上面调用的是后台进程(或者是吗?),如果不查看进程 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);//检查之间仅休眠 0.02 秒未设置($grep);exec("ps ax | grep ".$pid.".*javac", $grep);$found_process = false;//遍历grep的结果while(!$found_process && list(, $proc) = each($grep)) {$boom =explode(" ", $proc);$npid = (int)$boom[0];如果($npid == $pid)$found_process = true;}$done_compile = !$found_process;}如果(!done_compile)exec("kill -9 ".$pid);//...
...这似乎不起作用.至少在某些时候.通常,test.php
在 javac
甚至停止之前就开始运行,导致 test.php
无法找到主类当它尝试运行 java 程序时.我认为出于某种原因绕过了循环,尽管情况可能并非如此.在其他时候,整个评分系统会按预期工作.
同时,test.php
也使用了相同的策略(使用 X 秒循环和 grep)在某个时间限制内运行程序,它也有类似的错误.>
我认为错误在于 grep
没有找到 javac
的 pid,即使 javac
仍在运行,导致 10 秒循环早早中断.你能发现一个明显的错误吗?一个更谨慎的错误?我对 exec
的使用有问题吗?$out
有问题吗?还是发生了完全不同的事情?
感谢您阅读我的长问题.感谢所有帮助.
我刚刚想出了这段代码,它会运行一个进程,如果它运行的时间超过 $timeout
秒就终止它.如果它在超时之前终止,它将在 $output
中有程序输出,在 $return_value
中有退出状态.
我已经对其进行了测试,它似乎运行良好.希望您可以根据自己的需要进行调整.
array("pipe", "r"),//stdin 是一个管道,孩子将从中读取1 =>array("pipe", "w"),//stdout 是一个孩子将写入的管道2 =>array("file", "/tmp/error-output.txt", "a")//stderr 是一个要写入的文件);//启动进程$process = proc_open($command, $descriptorspec, $pipes, $cwd, $env);$startTime = 时间();$终止=假;$输出 = '';如果(is_resource($process)){//进程已启动//$pipes 现在看起来像这样://0 =>连接到子标准输入的可写句柄//1 =>连接到子标准输出的可读句柄//任何错误输出都将附加到/tmp/error-output.txt//无限循环直到超时或进程结束为了(;;) {usleep(100000);//不要消耗太多资源$stat = proc_get_status($process);//获取进程信息if ($stat['running']) {//仍在运行if (time() - $startTime > $timeout) {//检查超时//关闭描述符fclose($pipes[1]);fclose($pipes[0]);proc_terminate($process);//终止进程$return_value = proc_close($process);//获取返回值$终止=真;休息;}} 别的 {//进程在超时前完成$output = stream_get_contents($pipes[1]);//获取命令输出//关闭描述符fclose($pipes[1]);fclose($pipes[0]);proc_close($process);//关闭进程$return_value = $stat['exitcode'];//设置退出代码休息;}}如果(!$终止){回声$输出;}echo "命令返回 $return_value
";if ($terminated) echo "进程因长时间执行而终止
";} 别的 {echo "启动进程失败!
";}
参考:proc_open()、proc_close(), proc_get_status(), proc_terminate()
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.
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.
<?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
";
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()
这篇关于在 Linux 上创建 PHP 在线评分系统:exec 行为、进程 ID 和 grep的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!