2009-10-20 17 views
6

Tengo un script Perl que usa una herramienta externa (cleartool) para reunir información sobre una lista de archivos. Quiero usar IPC para evitar genere un nuevo proceso para cada archivo:¿Cómo hago una lectura de IPC sin bloqueo en Windows?

use IPC::Open2; 
my ($cin, $cout); 
my $child = open2($cout, $cin, 'cleartool'); 

comandos que devuelven esquemas eléctricos funcionan bien. p.ej.

print $cin "describe -short $file\n"; 
my $description = <$cout>; 

comandos que devuelven varios líneas me tienen en un callejón sin salida para la forma de consumir toda la respuesta sin quedar atrapado por un bloqueo de lectura:

print $cin "lshistory $file\n"; 
# read and process $cout... 

He tratado de establecer el gestor de archivo de sin bloqueo lee a través de fcntl:

use Fcntl; 
my $flags = ''; 
fcntl($cout, F_GETFL, $flags); 
$flags |= O_NONBLOCK; 
fcntl($cout, F_SETFL, $flags); 

pero Fcntl muere con el mensaje "Su proveedor no ha definido Fcntl macro F_GETFL."

He intentado usar IO :: Handle para establecer $cout->blocking(0) pero eso falla (devuelve undef y establece $! en "Error desconocido").

He intentado utilizar select para determinar si hay datos disponibles antes de intentar leer:

my $rfd = ''; 
vec($rfd, fileno($cout), 1) = 1; 
while (select($rfd, undef, undef, 0) >= 0) { 
    my $n = read($cout, $buffer, 1024); 
    print "Read $n bytes\n"; 
    # do something with $buffer... 
} 

pero que cuelga, sin leer nada. ¿Alguien sabe cómo hacer que esto funcione (en Windows)?

+0

¿Qué planea hacer sin bloquear en IO? – jrockway

+0

Quiero leer hasta que deje de obtener datos, lo que parece ser más confiable que intentar determinar que he llegado al final analizando los datos que ya recuperé. (No hay indicación de "esta es la última línea" en los datos). –

+0

Buscando un problema relacionado, encontré este compromiso en GitHub, que parece invocar magia negra para hacer un socket no bloqueante: https: // github .com/kthakore/frozen-bubble/commit/735dd2307455e32c69827e013992c2d022cb7347 –

Respuesta

5

select only works on sockets en Windows. Parece que IPC :: OpenX usa manejadores de archivos normales, por lo que no podrá usar select con los identificadores que crea.

Si no necesita el tiempo de espera/detección de actividad que selecciona, puede configurar los controladores para que sean no bloqueantes y solo lean o escriban como lo hace normalmente.

Si necesita un control más matizado, IPC::Run puede funcionar bien para usted.

También puede consultar la creación de un socketpair y usar esos identificadores con los procesos secundarios. Los perls más nuevos (5.8 y superiores) admiten la emulación socketpair en Windows utilizando sockets TCP.

Si intenta clonar STDOUT y STDERR para un programa que se ejecuta sin una consola (es decir, se inicia con wperl, en lugar de perl), no podrá obtener datos a través de STDIO.

En la práctica, esto ha sido un gran dolor para mí en varios proyectos. Lo que encontré que funcionó mejor fue escribir el proceso hijo para conectarse al servidor principal a través de TCP. Si no controla los procesos secundarios, consulte IPC::Run o socketpair.

+1

Sí, Windows apesta. Cuando se ejecuta allí, IPC :: Run realiza el truco del socket que mencionas. – ephemient

+0

Como escribí en la pregunta, no pude hacer que el identificador sea no bloqueante mediante Fcntl o IO :: Handle. Si conoce una forma de hacerlo (que funciona en Windows), compártalo. –

+0

@Michael: ejecuta el IO de bloqueo en un proceso separado. Comuníquese de ida y vuelta con el proceso principal utilizando sockets. El proceso hijo puede bloquearse, pero el proceso principal puede realizar E/S sin bloqueo en los zócalos de comunicaciones. Eso es lo que daotoad sugiere con 'socketpair'. – ephemient

0

IO no bloqueante es difícil en Windows. En este caso, se puede enviar la salida de cleartool a un archivo normal, y para utilizar seek a restablecer el indicador EF en el archivo cada vez que fuera leído del archivo:

my($cin, $cout); 
my $somefile = "some/file"; 
open($cin, "| cleartool > $somefile"); 
open($cout, '<', $somefile); 

... 

print $cin "$command_for_cleartool\n"; 
# if necessary, wait until cleartool finishes with new output 
seek $cout, 0, 1;  # clear eof condition from previous read 
my @cleartool_output = <$cout>; # capture recent output 
... process output ... 

Aunque esto probablemente no va a funcionar que bien si cleartool almacena su salida.

1

Otro kludge es usar sysread con un tamaño de búfer grande o improbable.

print $cin "$command_for_cleartool\n"; 
    my ($description, $maxlen, $buffer) = ("", 65336); 
    while (my $n = sysread $cout, $buffer, $maxlen) { 
     $description .= $buffer; 
     last if $n < $maxlen; 
    } 
    ... do something with $description ... 

sysread colgará si hay exactamente 0 bytes de entrada en espera de ser leído. Entonces, el código anterior se bloqueará si cleartool produce exactamente algún múltiplo de 65336 bytes. Si conoce un buen límite superior en el tamaño de la salida del programa, puede usar ese valor para $maxlen arriba. De lo contrario, podría elegir un número grande e improbable y rezar ...

Cuestiones relacionadas