在 Linux 上创建 PHP 在线评分系统:exec 行为、进程 ID 和 grep
背景
我正在使用 PHP 和 MySQL 编写一个简单的在线法官(代码评分系统)。它接受提交的 C++ 和 Java 代码,对其进行编译并测试。
这是在旧版本 Ubuntu 上运行 PHP 5.2 的 Apache。
我目前正在做的事情
是我有一个无限循环的php程序,每隔
//for(infinity)
exec("php -f grade.php");
//...
十分之一秒调用另一个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([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);
//...
在 compile.php
中了。它正在重定向 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)在一定的时间限制内运行程序,并且它有类似的错误,
我认为错误在于 。即使 javac
仍在运行,grep
也找不到 javac
的 pid,导致 10 秒循环提前中断。你能发现一个明显的错误吗?一个更谨慎的错误?我的 exec
使用有问题吗? $out
有问题吗?或者正在发生完全不同的事情?
感谢您阅读我的长问题。感谢所有帮助。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我刚刚想出了这段代码,它将运行一个进程,并在运行时间超过
$timeout
秒时终止它。如果它在超时之前终止,则会在$output
中包含程序输出,并在$return_value
中包含退出状态。我已经测试过了,看起来效果很好。希望您能根据您的需要进行调整。
参考文献:proc_open()、proc_close(), proc_get_status(), proc_terminate()
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.
References: proc_open(), proc_close(), proc_get_status(), proc_terminate()