2012-04-05 24 views
9

Fui a través de la documentación de open3 y aquí está la parte que no podía comprender:¿Por qué IPC :: Open3 se bloquea?

If you try to read from the child's stdout writer and their stderr writer, you'll have problems with blocking, which means you'll want to use select() or the IO::Select, which means you'd best use sysread() instead of readline() for normal stuff.

This is very dangerous, as you may block forever. It assumes it's going to talk to something like bc, both writing to it and reading from it. This is presumably safe because you "know" that commands like bc will read a line at a time and output a line at a time. Programs like sort that read their entire input stream first, however, are quite apt to cause deadlock.

Así que probé open3 la esperanza de conocerlo mejor. Este es el primer intento:

sub hung_execute { 
    my($cmd) = @_; 
    print "[COMMAND]: $cmd\n"; 
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd); 
    print "[PID]: $pid\n"; 
    waitpid($pid, 0); 
    if(<$err>) { 
     print "[ERROR] : $_" while(<$err>); 
     die; 
    } 
    print "[OUTPUT]: $_" while (<$out>); 
} 

Es interesante observar que debo inicializar $err aquí.

De todos modos, esto simplemente se bloquea cuando I execute("sort $some_file"); dado que $some_file es un archivo de texto que contiene más de 4096 caracteres (límites para mi máquina).

Entonces miré en this Preguntas y abajo era mi nueva versión de ejecutar:

sub good_execute { 
    my($cmd) = @_; 
    print "[COMMAND]: $cmd\n"; 
    my $in = gensym(); 
    #--------------------------------------------------- 
    # using $in, $out doesn't work. it expects a glob? 
    local *OUT = IO::File->new_tmpfile; 
    local *ERR = IO::File->new_tmpfile; 
    my $pid = open3($in, ">&OUT", ">&ERR", $cmd); 
    print "[PID]: $pid\n"; 
    waitpid($pid, 0); 
    seek $_, 0, 0 for \*OUT, \*ERR; 
    if(<ERR>) { 
     print "[ERROR] : $_" while(<ERR>); 
     die; 
    } 
    print "[OUTPUT]: $_" while (<OUT>); 
} 

El comando sort ejecuta bien ahora, pero no puedo entender por qué.

[Actualización] Después de leer la respuesta de @ tchrist, leí IO::Select, y después de un poco más de google, han llegado con esta versión de execute:

sub good_execute { 
    my($cmd) = @_; 
    print "[COMMAND]: $cmd\n"; 
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd); 
    print "[PID]: $pid\n"; 
    my $sel = new IO::Select; 
    $sel->add($out, $err); 
    while(my @fhs = $sel->can_read) { 
     foreach my $fh (@fhs) { 
      my $line = <$fh>; 
      unless(defined $line) { 
       $sel->remove($fh); 
       next; 
      } 
      if($fh == $out) { 
       print "[OUTPUT]: $line"; 
      }elsif($fh == $err) { 
       print "[ERROR] : $line"; 
      }else{ 
       die "[ERROR]: This should never execute!"; 
      } 
     } 
    } 
    waitpid($pid, 0); 
} 

Esto está trabajando muy bien, y algunas cosas se han vuelto más claros ahora. Pero la imagen general todavía es un poco confusa.

Así que mis preguntas son:

  1. ¿Qué pasa con hung_execute?
  2. Supongo que good_execute funciona debido a >& en la llamada open3. Pero, ¿por qué y cómo?
  3. Además, good_execute no funcionó cuando usé variables léxicas (my $out en lugar de OUT) para manejadores de archivos. Dio este error: open3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument. ¿Porque?
  4. Parece que solo uno de los manejadores de archivo puede escribir en un momento determinado, y si descarto el controlador que se queda en el recurso, los otros manejadores siguen esperando. Solía ​​pensar que STDERR y STDOUT eran transmisiones independientes y no compartían ningún recurso. Supongo que mi entendimiento es un poco defectuoso aquí. Por favor, dame algunos consejos sobre esto también.

Respuesta

13

Ha encontrado los mismos problemas que escribí en la documentación, y algunos más. Estás bloqueado porque esperas que el niño salga antes de leerlo. Si tiene más que un buffer de salida, se bloqueará y saldrá próximamente. Además, no ha cerrado los extremos de las manijas.

Tiene otros errores, también. No puede probar la salida en un manipulador de esa manera, porque acaba de hacer una línea de lectura de bloqueo y descarta sus resultados. Además, si intenta leer todo el stderr antes de la salida estándar, y si hay más que un buffer de salida de salida en stdout, su hijo bloqueará la escritura en stdout mientras bloquea la lectura de su stderr.

Realmente tiene que usar select, o IO::Select, para hacer esto correctamente.Solo debe leer desde un identificador cuando hay salida disponible en ese identificador, y tampoco debe mezclar llamadas almacenadas en el búfer con select, a menos que tenga mucha suerte.

+0

He leído en el módulo 'IO :: Select', y he actualizado mi pregunta ... – Unos

+0

@Unos Usted tiene muchas preguntas. Se supone que debes hacer solo una pregunta. Ya respondí a los iniciales, pero vuelves a preguntar las mismas cosas como si no te hubieras dado cuenta. Supongo que responder todas las preguntas nuevas requiere un párrafo o tres para casi cada línea de tu código en cada programa. Eso es mucho trabajo para pedirle a alguien, ciertamente más de una hora y lo más probable es que trabaje tres horas de trabajo gratis. Simplemente no tengo ese tiempo hoy. Por favor, estudie lo que ya dije, porque no veo que se haya hundido. – tchrist

+0

Hola, @tchrist, no quería irritarte. No eliminé mis preguntas anteriores después de la actualización porque pensé que las respuestas podrían perder contexto si lo hiciera. Definitivamente estudiaré esto con más detalle. – Unos

7

hung_execute:

Parent      Child 
------------------------ ------------------------ 
Waits for child to exit 
          Writes to STDOUT 
          Writes to STDOUT 
          ... 
          Writes to STDOUT 
          Tries to write to STDOUT 
           but the pipe is full, 
           so it blocks until the 
           pipe is emptied some. 

punto muerto!


good_execute:

Parent      Child 
------------------------ ------------------------ 
Waits for data 
          Writes to STDOUT 
Reads the data 
Waits for data 
          Writes to STDOUT 
Reads the data 
Waits for data 
...      ... 
          Writes to STDOUT 
Reads the data 
Waits for data 
          Exits, closing STDOUT 
Reads EOF 
Waits for child to exit 

El tubo podría llenarse, bloqueando el niño; pero el padre vendrá a vaciarlo lo suficientemente pronto, desbloqueando al niño. Sin punto muerto


">&OUT" evalúa a >&OUT. (Sin variables para interpolar)

">&$OUT" evalúa a >&GLOB(0x########). (Ha interpolado $OUT.)

Hay una forma de pasar identificadores de archivos léxicos (o más bien su descriptor), pero hay un error relacionado con ellos, por lo que siempre uso variables de paquete con open3.


stdout y stderr son independientes (a menos que haga algo así como 2>&1, e incluso entonces, van a tener banderas separadas y tampones). Llegaste a la conclusión equivocada si descubrías que no es así.

+0

Gracias por las fotos, @ikegami. Estaba pensando en una dirección totalmente diferente. Sin embargo, tiene sentido ahora. – Unos

Cuestiones relacionadas