El problema
que estaba usando una función que hace uso de proc_open()
para invocar comandos shell. Parece que la forma en que estaba haciendo STDIO era incorrecta y algunas veces causaba que PHP o el comando objetivo se bloqueen. Este es el código original:ejecución shell Proper en 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));
}
Funciona mayor parte del tiempo, pero eso no es suficiente, quiero que funcione siempre.
El problema radica en stream_get_contents()
encerrar si las memorias intermedias Stdio exceden 4k de datos.
caso de prueba
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__));
}
Nos salida de una cadena doble en STDERR y stdout con la longitud combinada de 4120 bytes (superiores a 4k). Esto hace que PHP se bloquee en ambos lados.
Solución
Al parecer, stream_select()
es el camino a seguir. Tengo el siguiente código:
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
}
}
La única pieza restante del rompecabezas está manejando STDIN (ver la línea marcada (???)
). Me di cuenta de que STDIN debe ser suministrado por lo que sea que llame a mi función, execute()
. Pero, ¿y si no quiero usar STDIN en absoluto? En mi caso de prueba, arriba, no pedí comentarios, pero se supone que debo hacer algo con STDIN.
Dicho esto, el enfoque anterior todavía se congela en stream_get_contents()
. No estoy muy seguro de qué hacer/probar a continuación.
Créditos
La solución fue sugerido por Jakob Truelsen, así como el descubrimiento de la emisión original. El consejo de 4k también fue su idea. Antes de esto, estaba desconcertado sobre por qué la función funcionaba bien (no sabía que todo dependía del tamaño del búfer).
Si no planea enviar ningún dato, no necesita hacer nada con STDIN. Al igual que '$ except', podría simplemente configurar' $ write' para que sea nulo. – Zoredache
@Zoredache Pero mi problema no es con STDIN, sino STDOUT. Además, esta es la misma razón por la que le dije a Jay (abajo). – Christian