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