Я пишу простой онлайн-судья (система оценки кода) с использованием PHP и MySQL. Он принимает представленные коды на C ++ и Java, компилирует их и тестирует их.
Это Apache работает на PHP 5.2 на старой версии Ubuntu.
У меня есть php-программа, которая бесконечно петляет, вызывая другую php-программу
//for(infinity) 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([all tests]) exec("php -f test.php"); //...
(Контрольная точка: compile.php
должен полностью завершить работу до того, как цикл «for» compile.php
test.php
даже начнется, правильно?)
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
должен захватывать идентификатор процесса javac
в $out[0]
.
Я хочу прекратить компиляцию, если по какой-то причине компиляция занимает более 10 секунд, и я хочу завершить compile.php
если программа прекращает компиляцию до 10 секунд. Поскольку exec("javac...
я вызываю выше – фоновый процесс (или это?), Я не знаю, когда он завершил, не обращаясь к идентификатору процесса, который ранее был сохранен в $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); //...
с//... $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-second и grep) при запуске программы в определенный срок, и у нее есть аналогичная ошибка.
Я думаю, что ошибка заключается в том, что grep
не обнаруживает javac
, даже когда javac
все еще работает, в результате чего 10-секундный цикл прерывается раньше. Можете ли вы заметить очевидную ошибку? Более сдержанная ошибка? Есть ли проблема с моим использованием exec
? Есть ли проблема с $out
? Или что-то совсем другое происходит?
Спасибо, что прочитали мой длинный вопрос. Вся помощь приветствуется.
Я просто придумал этот код, который будет запускать процесс, и прекратить его, если он будет работать дольше, чем $timeout
seconds. Если он заканчивается до истечения таймаута, он будет иметь выход программы в $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 ()