在 Linux 上创建 PHP 在线评分系统:exec 行为、进程 ID 和 grep [英] Creating a PHP Online Grading System on Linux: exec Behavior, Process IDs, and grep

查看:27
本文介绍了在 Linux 上创建 PHP 在线评分系统:exec 行为、进程 ID 和 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.phptest.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.phpjavac 甚至停止之前就开始运行,导致 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆