2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




 
 Как ограничить время выполнения внешней программы?
Сообщение06.04.2015, 16:48 
В PHP скрипте запускаю внешнюю программу через shell_exec(). Но иногда она зависает. Как из скрипта запускать с возможностью прервать выполнение по истечении определенного времени?

Есть ли какие-то команды в shell, ограничивающие время работы процесса?

Если нет, то как можно организовать убийство процесса в PHP? Как отследить время? Sleep() с последующим убийством не подходит, т.к. при успешном завершении, необходимо тут же продолжить, а не спать.

Пока придумал только так
код: [ скачать ] [ спрятать ]
Используется синтаксис PHP
<?php
        $pipes = [];
        $options = [
            0 => ['pipe', 'r'],
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w']
        ];
       
        $pid = proc_open('filename', $options, $pipes );
       
        $breaktime = microtime() + TIMELIMIT;
     
        while( microtime() < $breaktime )
        {
           $status = proc_get_status($pid);
           if( $status['running'] == false ) break;
           usleep(10); // ждем 10 микросекунд
        }
       
        proc_close($pid);
    ?>

Так будет нормально или такой частный цикл будет слишком нагружать? Как вообще положено убивать дочерние процессы по таймауту?

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение06.04.2015, 18:01 
Да собственно крутится в цикле ожидания по таймауту, как вы и написали, может только ждать не 10 а подольше, если нагрузка на проц упадёт.
Но тут ньюансы, смотрите описания к функциям внимательно и желательно читайте комментарии на php.net к функциям.
Вы например не делаете обязательного закрытия пайпов, без чего дочерний процесс может как раз зависнуть - это прямо написано в описании php_close.
Далее, если дело доходит всё таки до принудительной терминации, то обратите пристальное внимание на первый же пользовательский комментарий в http://php.net/manual/en/function.proc-terminate.php

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение06.04.2015, 22:01 
Цитата:
Вы например не делаете обязательного закрытия пайпов, без чего дочерний процесс может как раз зависнуть - это прямо написано в описании php_close.
Спасибо за совет. Я не обратил внимание на это.

Еще проблема возникла. Программа dvipng самоуничтожается, устанавливая в статус [running] => 0, а программа latex, видимо, нет. У него в статусе [running] => 1 установлен до конца, до убийства по таймауту. Может он не убивается и живет сам по себе как демон, принимая запросы? Как же в shell_exec() определяется, что пора выходить? Видимо, в shell_exec() ожидается вывод внешней программы.

Может как-то использовать stream_select. Так будет корректно работать? Не произойдет ли убийство раньше времени? :roll:

Код:
$pipes = array();
  $options = array(
     0 => array('pipe', 'r'),
     1 => array('pipe', 'w'),
     2 => array('pipe', 'w')
  );
 
  $pid = proc_open( $cmd, $options, $pipes, $cwd );

  $r_stms = array( $pipes[1], $pipes[2] );
  $w_stms = NULL;
  $e_stms = NULL;
 
  stream_select( $r_stms, $w_stms, $e_stms, $timelimit );

  fclose($pipes[0]);
  fclose($pipes[1]);
  fclose($pipes[2]);
  proc_close($pid);

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение06.04.2015, 23:47 
Похоже, stream_select вообще не ждет вывода, а почему-то сразу выходит.

код: [ скачать ] [ спрятать ]
Используется синтаксис PHP
$start = microtime(1);

echo '$start = '.$start.'<br>';

$pid = proc_open( 'sleep 30', $options, $pipes, $cwd )

$proc_status = proc_get_status($pid);

print_r($proc_status);

$r_stms = array($pipes[1],$pipes[2]);
$w_stms = NULL;
$e_stms = NULL;
$ch_strm = stream_select( $r_stms, $w_stms, $e_stms, 30 );

echo '$ch_strm = '.$ch_strm.'<br>';

fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($pid);

$end = microtime(1);
echo '$end = '.$end.'<br>';
echo '$d = '.($end-$start).'<br>';


Выводится следующее:
Код:
$start = 1428351165.01
Array ( [command] => sleep 30 [pid] => 6832 [running] => 1 [signaled] => [stopped] => [exitcode] => -1 [termsig] => 0 [stopsig] => 0 )
$ch_strm = 2
$end = 1428351165.06
$d = 0.0482149124146


-- Вт апр 07, 2015 00:49:14 --

Вопрос в том, как вызываемая программа сообщает shell_exec, что пора выходить. Вернее, как shell_exec узнает, что пора завершаться.

В мануале написано:
Цитата:
shell_exec — Выполняет команду через шелл и возвращает полный вывод в виде строки.

Возвращаемые значения
Вывод исполняемой команды или NULL, если произошла ошибка или команда ничего не вывела.

Замечание:
Эта функция может вернуть NULL и в случае ошибки и в случае, если программа ничего не вывела. Нельзя определить неудачный запуск с помощью этой функции. Если требуется получить код завершения программы используйте exec().

Как правильно это интерпретировать.. Можно ли утверждать, что shell_exec просто ждет вывода. Может программа как-то сообщает, что вывод закончен?

Или же shell_exec выходит, отдает выходной поток вызывающему скрипту, но при этом не убивает программу, давая возможность ей продолжать вывод?

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение07.04.2015, 02:07 
ellipse в сообщении #1001013 писал(а):
Как правильно это интерпретировать.. Можно ли утверждать, что shell_exec просто ждет вывода. Может программа как-то сообщает, что вывод закончен?
Нет, shell_exec просто ждёт завершения работы программы. И имено так программа и сообщает, что вывод закончен: просто заканчивает работу, завершает свой процесс.

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение07.04.2015, 02:24 
warlock66613 в сообщении #1001042 писал(а):
Нет, shell_exec просто ждёт завершения работы программы. И имено так программа и сообщает, что вывод закончен: просто заканчивает работу, завершает свой процесс.
Как она сообщает о завершении?

Вот такой отладочный код:
код: [ скачать ] [ спрятать ]
Используется синтаксис PHP
  $timelimit = 10; // 10 сек.
  $cmd = '/Server/texlive/2014/bin/win32/latex myfile.tex';
  $cwd = '/Server/htdocs/tex/tmp/';

  $pipes = array();
  $options = array(
     0 => array('pipe', 'r'),
     1 => array('pipe', 'w'),
     2 => array('pipe', 'w')
  );

  $start = microtime(1);
  echo '$start = '.$start.'<br>';
 
  $breaktime = microtime(1) + $timelimit;

  if( $pid = proc_open( $cmd, $options, $pipes, $cwd ) )
  {
    while( microtime(1) < $breaktime )
    {
      $proc_status = proc_get_status($pid);

      echo '<br>$proc_status = '; print_r($proc_status);

      if( $proc_status['running'] == false )
      {
        echo 'break!<br>';
        break;
      }
      usleep(200000);
    }
   
    $proc_status = proc_get_status($pid);
    echo '<br>$end_proc_status = ';  print_r($proc_status);
    echo '<br><br>';
   
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($pid);
  }
 
  $end = microtime(1);
  echo '$end = '.$end.'<br>';  
  echo '$d = '.($end-$start).'<br><br>';


выводит:

Код:
$start = 1428361887.33

$proc_status = Array ( [command] => /Server/texlive/2014/bin/win32/latex myfile.tex [pid] => 6420 [running] => 1 [signaled] => [stopped] => [exitcode] => -1 [termsig] => 0 [stopsig] => 0 )
$proc_status = Array ( [command] => /Server/texlive/2014/bin/win32/latex myfile.tex [pid] => 6420 [running] => 1 [signaled] => [stopped] => [exitcode] => -1 [termsig] => 0 [stopsig] => 0 )

и так еще 10050 раз. и наконец

$proc_status = Array ( [command] => /Server/texlive/2014/bin/win32/latex myfile.tex [pid] => 6420 [running] => 1 [signaled] => [stopped] => [exitcode] => -1 [termsig] => 0 [stopsig] => 0 )
end_proc_status = Array ( [command] => /Server/texlive/2014/bin/win32/latex myfile.tex [pid] => 6420 [running] => 1 [signaled] => [stopped] => [exitcode] => -1 [termsig] => 0 [stopsig] => 0 )

$end = 1428361897.55
$d = 10.2151138783
То есть висели в цикле ровно 10 сек, но так и не дождались в статусе процесса [running] => 0. Хотя очевидно, что latex отработал раньше. Файл dvi появляется мгновенно. За 10 сек можно не спеша в этом убедиться.

Когда запускаю dvipng, тогда все нормально работает. Никто не висит 10 секунд в цикле. [running] => 1 после нескольких итераций устанавливается в [running] => 0 и выходим из цикла по break.

Видимо, latex работатет в фоновом режиме и не умирает при завершении работы с tex-файлом. Как узнать, что он завершил работу?

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение07.04.2015, 03:08 
Попробуйте вот это делать сразу после proc_open, а не в конце:

Используется синтаксис PHP (brief)
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);


Т.е. latex вероятно определяет, что вы ему в stdin подсунули канал, и ждет, что вы ему туда пришлете документ на обработку, потому и не завершается. По-хорошему, 1-й и 2-й каналы надо не закрывать, а куда-то перенаправить - в /dev/null или еще куда-то, смотря какая ОС (и куда вам нужно).

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение07.04.2015, 04:43 
odelschwank, помогло. Большое спасибо!

Как же лучше определить эти параметры для proc_open.

Интересно, можно ли передать пустые массивы options и pipes. И куда будут направлены потоки, если передать пустые массивы?

Используется синтаксис PHP
$pipes = array();
$options = array();
$pid = proc_open( $cmd, $options, $pipes, $cwd );


Цитата:
По-хорошему, 1-й и 2-й каналы надо не закрывать, а куда-то перенаправить - в /dev/null или еще куда-то, смотря какая ОС (и куда вам нужно).

Допишу в конце команды >nul 2>&1.

 
 
 
 Re: Как ограничить время выполнения внешней программы?
Сообщение07.04.2015, 14:23 
Написал программку, которая спит заданное число сек. Протестировал на ней. Оказывается, proc_close не убивает процесс, а ждет окончания его работы. :?

Как узнать, latex и dvipng создают дочерние процессы, которые могут осиротеть?

 
 
 [ Сообщений: 9 ] 


Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group