2012-04-05 12 views
9

Sono andato attraverso la documentazione di open3 e qui è la parte che non riuscivo a capire:Perché IPC :: Open3 diventa deadlock?

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.

Così ho provato open3 sperando di conoscerla meglio. Ecco il primo tentativo:

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>); 
} 

E 'interessante notare che devo inizializzare $err qui.

In ogni caso, questo si blocca solo quando I execute("sort $some_file"); dato che $some_file è un file di testo contenente più di 4096 caratteri (limiti per la mia macchina).

Poi ho guardato in this FAQ, e sotto era la mia nuova versione di eseguire:

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>); 
} 

Il comando sort esegue bene ora, ma non riesco a capire perché.

[Update] Dopo aver letto la risposta di @ tchrist, ho letto IO::Select, e dopo un po 'googling, hanno creato questa versione di 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); 
} 

Questo sta lavorando bene, e alcune cose sono diventato più chiaro ora. Ma il quadro generale è ancora un po 'confuso.

Quindi le mie domande sono:

  1. Cosa c'è di sbagliato con hung_execute?
  2. Immagino che good_execute funzioni a causa dello >& nella chiamata open3. Ma perché e come?
  3. Inoltre, good_execute non ha funzionato quando ho usato variabili lessicali (my $out anziché OUT) per filehandle. Ha dato questo errore: open3: open(GLOB(0x610920), >&main::OUT) failed: Invalid argument. Perchè così?
  4. Sembra che solo uno degli handle di file possa scrivere in un determinato momento e se si elimina l'handle che si trattiene sulla risorsa, gli altri handle continuano ad attendere. Ero solito pensare che STDERR e STDOUT fossero flussi indipendenti e non condividessero alcuna risorsa. Credo che la mia comprensione sia un po 'imperfetta qui. Gentilmente dammi alcune indicazioni su questo anche.

risposta

13

Hai incontrato gli stessi problemi che ho scritto nella documentazione, e poi alcuni. Stai bloccando perché stai aspettando che il bambino esca prima di leggere da esso. Se ha più di un buffer di pipe di output, bloccherà e uscirà. Inoltre non hai chiuso le estremità delle maniglie.

Hai anche altri errori. Non è possibile testare l'output su un handle in questo modo, perché si è appena eseguito un blocco di readline e scartati i risultati. Inoltre, se provi a leggere tutto lo stderr prima dello stdout, e se c'è più di un buffer pipe di output su stdout, allora tuo figlio bloccherà la scrittura sullo stdout mentre blocchi la lettura dal suo stderr.

È davvero necessario utilizzare select o IO::Select, per farlo correttamente.È necessario leggere solo da un handle quando è disponibile l'output su tale handle e non è necessario combinare le chiamate memorizzate nel buffer con select, a meno che non si sia molto fortunati.

+0

Ho letto il modulo 'IO :: Select' e ho aggiornato la mia domanda ... – Unos

+0

@Unos Hai un sacco di domande. Dovresti chiedere solo una domanda. Ho già risposto agli iniziali, ma tu ripeti le stesse cose come se non te ne fossi accorto. Immagino che rispondere a tutte le tue nuove domande richieda un paragrafo o tre per quasi ogni riga del tuo codice in ogni programma. È un bel po 'di lavoro da chiedere a qualcuno, sicuramente più di un'ora e molto probabilmente andando avanti per tre ore di lavoro gratuito. Oggi non ho tempo. Per favore, studia quello che ho già detto, perché non vedo che è affondato. – tchrist

+0

ciao @tchrist, non volevo irritarti. Non ho rimosso le mie precedenti domande dopo l'aggiornamento poiché pensavo che le risposte avrebbero potuto perdere il contesto se l'avessi fatto. Lo studierò sicuramente più in dettaglio. – 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. 

Deadlock!


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 

Il tubo potrebbe ottenere pieno, bloccando il bambino; ma il genitore tornerà a svuotarlo abbastanza presto, sbloccando il bambino. Nessun deadlock.


">&OUT" viene valutato come >&OUT. (Nessuna variabile da interpolare)

">&$OUT" valutazioni a >&GLOB(0x########). (È interpolati $OUT.)

C'è un modo di passare handle di file lessicali (o meglio il suo descrittore), ma c'è un bug che li riguardano, così io uso sempre variabili del pacchetto con open3.


STDOUT e STDERR sono indipendenti (a meno che non si fa qualcosa di simile 2>&1, e anche allora, avranno bandiere e buffer separati). Sei arrivato alla conclusione sbagliata se hai scoperto che non lo sono.

+0

Grazie per le immagini, @ikegami. Stavo pensando in una direzione totalmente diversa. Ha senso ora però. – Unos