2013-09-16 4 views
3

Следующие скрипты отслеживают /dev/shm/test для новых файлов и выводит информацию об этом в режиме реального времени.proc_open оставляет зомби-процесс

Проблема заключается в том, что когда пользователь закрывает браузер, процесс inotifywait остается открытым и так далее.

Есть ли способ избежать этого?

<?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("pipe", "a") // stderr is a file to write to 
); 

$process = proc_open('inotifywait -mc -e create /dev/shm/test/', $descriptorspec, $pipes); 

if (is_resource($process)) { 

    header("Content-type: text/html;charset=utf-8;"); 
    ob_end_flush(); //ends the automatic ob started by PHP 
    while ($s = fgets($pipes[1])) { 
    print $s; 
    flush(); 
    } 
    fclose($pipes[1]); 
    fclose($pipes[0]); 
    fclose($pipes[2]); 

    // It is important that you close any pipes before calling 
    // proc_close in order to avoid a deadlock 
    $return_value = proc_close($process); 

    echo "command returned $return_value\n"; 
} 
?> 
+0

Ну ваш код содержит бесконечный цикл. Почему вы ожидаете, что он не сделает эту бесконечную петлю? Также: какой SAPI вы используете здесь? Что произойдет, например, если вы запустите этот скрипт в CLI и прервите его с помощью CTRL + C? Убит ли «детский процесс»? Открывает ли процесс proc_open дочерний процесс или независимый? И каков тайм-аут с 'fgets'? Я имею в виду, что вы делаете, если STDOUT не имеет никакого смысла читать - как долго это будет ждать? – hakre

+0

@hakre Я могу подтвердить, что он убивает процесс inotifywait с помощью CTRL + C в режиме консоли.fgets не имеет тайм-аута, потому что я пытаюсь проверить 'connection_aborted()' и 'proc_terminate()' как ответ Джона, но не помог, что странно, поскольку документировано, что PHP будет знать, что соединение было прервано только после попытки вывода что-то (что я и делаю). Кроме того, не проверяя эти функции, скрипт должен прекратить отмену загрузки окна, не так ли? – JorgeeFG

+0

Какую версию php вы используете? – Omiga

ответ

0

Вы можете использовать ignore_user_abort, чтобы указать, что сценарий не должен прекратить выполнение, когда пользователь закрывает окно браузера. Это позволит решить половину задачи, так что вы также должны проверить, если окно было закрыто внутри контура с connection_aborted, чтобы определить, когда необходимо закрыть все упорядоченно:

header("Content-type: text/html;charset=utf-8;"); 
ignore_user_abort(true); 
ob_end_flush(); //ends the automatic ob started by PHP 
while ($s = fgets($pipes[1])) { 
    print $s; 
    flush(); 
    if (connection_aborted()) { 
     proc_terminate($process); 
     break; 
    } 
} 
+0

Спасибо, это сработает для вас? Я пробовал каждую комбинацию того, что вы мне говорили, но мне не повезло, процесс остается открытым. Я даже создаю новый файл после того, как я отменил свой браузер для генерации вывода, и пусть PHP знает, что соединение было прервано, но не повезло. – JorgeeFG

+0

@Jorge: На самом деле это тоже не работает для меня. К сожалению, я не могу сейчас разобраться в этом, попытаюсь вернуться к нему позже. – Jon

+0

Это что-то странное, возможно, оно застряло на '$ s = fgets ($ pipes [1])' из-за неизвестного (по крайней мере для меня) языка. – JorgeeFG

0

ли эта помощь?

$proc_info = proc_get_status($process); 
pcntl_waitpid($proc_info['pid']); 
1

Это потому, что inotifywait будет ждать, пока изменения не произойдет в файл /dev/shm/test/, затем будет вывода диагностической информации на стандартной ошибки и информации о событиях на стандартный вывод и fgets() будет ждать, пока он не сможет прочитать строку: Чтение заканчивается, когда $length - 1 байт (2-й параметр) были прочитаны или новая строка (которая включена в возвращаемое значение) или EOF (в зависимости от того, что наступит раньше). Если длина не указана, она будет продолжать чтение из потока, пока не достигнет конца строки.

Так в основном, вы должны прочитать данные из дочернего процесса стандартный вывод трубы неблокируемых режим с stream_set_blocking($pipes[1], 0) или вручную проверить, есть ли данные по этой трубе с stream_select().

Кроме того, вам необходимо игнорировать прерывание пользователя с помощью ignore_user_abort(true).

0

Как inotifywait работает как собственный процесс, который в принципе никогда не заканчивается, вам нужно отправить сигнал KILL. Если вы запустили скрипт на кли, сигнал Ctrl + C также отправляется в процесс inotifywait, но у вас его нет при работе на веб-сервере.

Вы отправляете сигнал в функцию, которая вызывается register_shutdown_function или __destruct в классе.

Этой простая обертка proc_open может помочь:

class Proc 
{ 
    private $_process; 
    private $_pipes; 

    public function __construct($cmd, $descriptorspec, $cwd = null, $env = null) 
    { 
     $this->_process = proc_open($cmd, $descriptorspec, $this->_pipes, $cwd, $env); 
     if (!is_resource($this->_process)) { 
      throw new Exception("Command failed: $cmd"); 
     } 
    } 

    public function __destruct() 
    { 
     if ($this->isRunning()) { 
      $this->terminate(); 
     } 
    } 

    public function pipe($nr) 
    { 
     return $this->_pipes[$nr]; 
    } 

    public function terminate($signal = 15) 
    { 
     $ret = proc_terminate($this->_process, $signal); 
     if (!$ret) { 
      throw new Exception("terminate failed"); 
     } 
    } 

    public function close() 
    { 
     return proc_close($this->_process); 
    } 

    public function getStatus() 
    { 
     return proc_get_status($this->_process); 
    } 

    public function isRunning() 
    { 
     $st = $this->getStatus(); 
     return $st['running']; 
    } 
} 

$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("pipe", "a") // stderr is a file to write to 
); 
$proc = new Proc('inotifywait -mc -e create /dev/shm/test/', $descriptorspec); 

header("Content-type: text/html;charset=utf-8;"); 
ob_end_flush(); //ends the automatic ob started by PHP 
$pipe = $proc->pipe(1); 
while ($s = fgets($pipe)) { 
    print $s; 
    flush(); 
} 
fclose($pipe); 

$return_value = proc->close($process); 

echo "command returned $return_value\n"; 

Или вы можете использовать Process компонент Symfony, который делает exactly the same (плюс другие полезные вещи)

Смежные вопросы