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--;
}
Тем не менее, я бы не хотел полагаться на что-то вроде этого:
- У меня будут процессы, которые выполняются дольше минуты - о таких процессах будет ложно сообщено, что они были упомянуты выше
- Я хочу знать, когда 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
по вашей команде вы все равно можете прочитать выходные данные процесса, и он может закончиться быстрее (без ожидания неизменяемого тайм-аута)