Я использовал функцию, которая использовала proc_open()
для вызова команд оболочки. Кажется, что я делал STDIO неправильно, а иногда вызывал PHP или целевую команду для блокировки. Это исходный код:
function execute($cmd, $stdin=null){ $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); fwrite($pipes[0],$stdin); fclose($pipes[0]); $stdout=stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr=stream_get_contents($pipes[2]); fclose($pipes[2]); return array( 'stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc) ); }
устройствоfunction execute($cmd, $stdin=null){ $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); fwrite($pipes[0],$stdin); fclose($pipes[0]); $stdout=stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr=stream_get_contents($pipes[2]); fclose($pipes[2]); return array( 'stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc) ); }
Это работает большую часть времени , но этого недостаточно, я хочу заставить его работать всегда.
Проблема заключается в stream_get_contents()
, если буферы STDIO превышают 4 тыс. Данных.
function out($data){ file_put_contents('php://stdout',$data); } function err($data){ file_put_contents('php://stderr',$data); } if(isset($argc)){ // RUN CLI TESTCASE out(str_repeat('o',1030); err(str_repeat('e',1030); out(str_repeat('O',1030); err(str_repeat('E',1030); die(128); // to test return error code }else{ // RUN EXECUTION TEST CASE $res=execute('php -f '.escapeshellarg(__FILE__)); }
Мы выводим строку дважды в STDERR и STDOUT с общей длиной 4120 байт (более 4k). Это заставляет PHP блокироваться с обеих сторон.
По-видимому, stream_select()
– это путь. У меня есть следующий код:
function execute($cmd,$stdin=null,$timeout=20000){ $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); $write = array($pipes[0]); $read = array($pipes[1], $pipes[2]); $except = null; $stdout = ''; $stderr = ''; while($r = stream_select($read, $write, $except, null, $timeout)){ foreach($read as $stream){ // handle STDOUT if($stream===$pipes[1]) /*...*/ $stdout.=stream_get_contents($stream); // handle STDERR if($stream===$pipes[2]) /*...*/ $stderr.=stream_get_contents($stream); } // Handle STDIN (???) if(isset($write[0])) ; // the following code is temporary $n=isset($n) ? $n+1 : 0; if($n>10)break; // break while loop after 10 iterations } }
Единственный оставшийся фрагмент головоломки – обработка STDIN (см. Строку, помеченную (???)
). Я понял, что STDIN должен быть обеспечен тем, что вызывает мою функцию, execute()
. Но что, если я вообще не хочу использовать STDIN? В моем тестовом тесте, выше, я не просил ввода, но я должен что-то сделать для STDIN.
Тем не менее, вышеупомянутый подход все еще зависает в stream_get_contents()
. Я совершенно не уверен, что делать / попробовать дальше.
Решение было предложено Якобом Труэльсеном, а также обнаружение оригинальной проблемы. Идея 4k была его идеей. До этого я был озадачен тем, почему функция работает нормально (не знал, что все зависит от размера буфера).
Ну, кажется, год прошел и забыл, что эта вещь все еще ждет!
Тем не менее, я завернул этот беспорядок в хороший PHP-класс, который вы можете найти в Github .
Основная проблема заключается в том, что чтение STDERR заставляет скрипт PHP блокироваться, поэтому он отключен.
С яркой стороны, благодаря событиям и некоторому хорошему кодированию (надеюсь!), Можно фактически взаимодействовать с выполняемым процессом (отсюда и имя класса, InterExec
). Таким образом, вы можете иметь поведение стиля бота в PHP.
Вы пропустили это примечание в руководстве PHP для stream_select ():
Когда stream_select () возвращается, массивы read, write и except изменяются, чтобы указать, какие потоковые ресурсы фактически изменили статус.
Вам необходимо повторно создать массивы, прежде чем вызывать stream_select () каждый раз.
В зависимости от процесса, который вы открываете, это может быть причиной того, что ваш пример все еще блокируется.
while($r = stream_select($read, $write, $except, null, $timeout)){
Насколько я знаю, это установит $ r в количество измененных потоков, которое может быть 0, и цикл больше не будет продолжаться. Я бы лично перекодировал это, как описано в руководстве PHP:
while(false !== ($r = stream_select($read, $write, $except, null, $timeout))){
Что касается вашего STDIN, если ваш процесс не является интерактивным, STDIN может не понадобиться. Каков процесс, который вы выполняете?
Вся проблема с зависанием в stream_get_contents заключается в том, как создается процесс. Правильный способ – открыть STDOUT с режимом чтения / записи на трубе, например:
$descriptor = array (0 => array ("pipe", "r"), 1 => array ("pipe", "rw"), 2 => array ("pipe", "rw")); //Open the resource to execute $command $t->pref = proc_open($command,$descriptor,$t->pipes); //Set STDOUT and STDERR to non-blocking stream_set_blocking ($t->pipes[0], 0); stream_set_blocking ($t->pipes[1], 0);
Очевидно, что когда stream_get_contents хочет прочитать трубу STDOUT, ему нужен режим чтения. Та же ошибка с hang / freeze / block находится в этом хорошем классе https://gist.github.com/Arbow/982320
Затем блокировка исчезает. Но чтение ничего не читает.