процесс открытия зависает при попытке чтения из потока


Я столкнулся с проблемой с proc_open в Windows при попытке конвертировать wmv-файл (в flv) с помощью ffmpeg, однако я подозреваю, что столкнусь с тем же сценарием при возникновении определенных условий.
В основном мой код выглядит следующим образом:

$descriptorspec = array
(
    array("pipe", "r"),
    array("pipe", "w"),
    array("pipe", "w")
);

$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/project/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/project/Wildlife.flv"', $descriptorspec, $pipes);
var_dump(stream_get_contents($pipes[1]));

Теперь этот код приведет к зависанию PHP на неопределенный срок (не имеет значения, если вместо stream_get_contents я буду использовать fgets или stream_select, поведение будет согласованным).

Причина этого (я подозреваю) в том, что, в то время как поток STDOUT открыт успешно, процесс ничего в него не записывает (даже если выполнение той же команды в cmd выводит вывод), и поэтому попытка чтения из такого потока вызовет ту же проблему, что и описанная здесь, поэтому - PHP ожидает, что в потоке что-то есть, процесс ничего в него не записывает.

Однако (дополнительное удовольствие) установка stream_set_timeout или stream_set_blocking не оказывает никакого эффекта.

Как таковой - может ли кто-нибудь подтвердить/опровергнуть то, что происходит, и, если возможно, показать, как могу ли я справиться с такой ситуацией? Я просмотрел ошибки PHP, и все proc_open hangs, похоже, исправлены.

На данный момент я реализовал такое решение:

$timeout = 60;
while (true) {
    sleep(1);

    $status = proc_get_status($procedure);
    if (!$status['running'] || $timeout == 0) break;

    $timeout--;
}

Однако я бы действительно не хотел полагаться на что-то подобное, как:

  1. У меня будут процессы, которые будут выполняться дольше минуты - о таких процессах будет ложно сообщено, что они относятся к вышеупомянутому типу
  2. Я хочу знать, когда ffmpeg завершит преобразование видео - в настоящее время я буду знать только этот процесс все еще выполняется через минуту, и я ничего не могу сделать, чтобы проверить, есть ли какой-либо вывод (так как он будет зависать PHP).

Кроме того, я действительно не хочу ждать целую минуту, пока процесс будет проверен (например, преобразование данного видео из командной строки занимает <10 секунд), и у меня будут видео, для преобразования которых потребуется больше времени.


Согласно комментарию @Sjon, вот stream_select Я использовал, который блокируется из-за той же проблемы - стандартный вывод не записывается кому:

$descriptorspec = array
(
    array("pipe", "r"),
    array("pipe", "w"),
    array("pipe", "w")
);

$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv"', $descriptorspec, $pipes);

$read = array($pipes[0]);
$write = array($pipes[1], $pipes[2]);
$except = array();

while(true)
if(($num_changed_streams = stream_select($read, $write, $except, 10)) !== false)
{
    foreach($write as $stream)
        var_dump(stream_get_contents($stream));

    exit;
}
else
    break;

За разговор с @Sjon - чтение из буферизованных потоков в Windows нарушено. В конечном итоге решение состоит в том, чтобы использовать перенаправление потока через оболочку, а затем прочитать созданные файлы - как таковые

$descriptorspec = array
(
    array("pipe", "r"),
    array("pipe", "w"),
    array("pipe", "w")
);

$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.mp4" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv" > C:/stdout.log 2> C:/stderr.log', $descriptorspec, $pipes);

proc_close($procedure);

$output = file_get_contents("C:/stdout.log");
$error = file_get_contents("C:/stderr.log");

unlink("C:/stdout.log");
unlink("C:/stderr.log");

Поскольку поток буферизован, в файле мы получим небуферизованный вывод (то, что я тоже искал). И нам не нужно проверять, изменяется ли файл, потому что результат из оболочки небуферизован и синхронен.

Author: Community, 2015-07-02

1 answers

Для воспроизведения этого потребовалось некоторое время, но я нашел вашу проблему. Команда, которую вы запускаете, выводит некоторую диагностику при ее запуске; но она выводится не в стандартный вывод, а в стандартный вывод. Причина этого объясняется в man stderr:

При нормальных обстоятельствах каждая программа UNIX имеет три потока, открытых для нее при запуске: один для ввода, один для вывода и один для печати диагностических сообщений или сообщений об ошибках

Если бы вы правильно использовали потоки ; это это не было бы проблемой; но вместо этого вы звоните stream_get_contents($pipes[1]). Это приводит к тому, что PHP ожидает вывода из stdout, который никогда не приходит. Это исправление простое; вместо этого прочитайте из stderr stream_get_contents($pipes[2]), и скрипт завершит работу сразу после завершения процесса

Чтобы подробнее рассказать о вашем добавлении stream_select к вопросу; stream_select не реализован в Windows на php, об этом говорится в руководстве:

Использование stream_select() для файловых дескрипторов, возвращаемых proc_open(), завершится ошибкой и вернет значение FALSE под окнами.

Так что, если код, опубликованный выше, не сработает; я не уверен, что сработает. Рассматривали ли вы возможность отказа от своего решения для потоков, вместо этого вернувшись к простому вызову exec()? Если вы добавите >%TEMP%/out.log 2>%TEMP%/err.log к своей команде, вы все равно сможете считывать выходные данные процесса, и он может завершиться быстрее (не дожидаясь неизменяемого тайм-аута)

 1
Author: Sjon, 2015-07-07 19:44:55