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

Я столкнулся с проблемой с 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 Я использовал, какие блоки из-за той же проблемы - STDOUT не записывается в:

$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");

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

1 ответ

Решение

Это заняло некоторое время для воспроизведения, но я нашел вашу проблему. Команда, которую вы запускаете, выдает некоторую диагностику при ее запуске; но он выводит не на стандартный вывод, а на стандартный вывод. Причина этого объясняется в 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 в Windows.

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

Другие вопросы по тегам