php - 在 Linux : exec Behavior, 进程 ID 和 grep 上创建 PHP 在线评分系统

标签 php apache ubuntu exec javac

背景

我正在使用PHP和MySQL编写一个简单的在线判断(代码评分系统)。它接受提交的 C++ 和 Java 代码,编译并测试它们。

这是在旧版本的 Ubuntu 上运行 PHP 5.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);
//...

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); // 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 的 pid,即使 javac 仍在运行,导致 10 秒循环提前中断。你能发现一个明显的错误吗?一个更谨慎的错误?我对 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() , proc_terminate()

关于php - 在 Linux : exec Behavior, 进程 ID 和 grep 上创建 PHP 在线评分系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9356250/

相关文章:

php - Paypal 编程

c# - 如何使用带有摘要式身份验证和查询字符串的 WebClient.DownloadFile

apache - 如何在 Apache + Tomcat 设置上设置 SSL

shell - 如何使用 shell 脚本切断所有子文件夹中文本文件中每一行的尾部?

php - 为什么我的选择框生成器不显示表中的所有选项?

javascript - 分割值未正确计算

javascript - Ajax jquery mysqli 更新

php - 这些术语在 Apache Status 中的 CPU 使用率意味着什么?

python - 相同的代码,不同的错误 : Python init()

javascript - 在 HTML5 和 JavaScript 上为 ubuntu 13.04 编写的小部件