Come intercetto l'output senza buffer di un Proc :: Async in Raku?
Con uno snippet come
# Contents of ./run
my $p = Proc::Async.new: @*ARGS; react { whenever Promise.in: 5 { $p.kill }
whenever $p.stdout { say "OUT: { .chomp }" } whenever $p.ready { say "PID: $_" } whenever $p.start { say "Done" }
}
eseguito come
./run raku -e 'react whenever Supply.interval: 1 { .say }'
Mi aspettavo di vedere qualcosa di simile
PID: 1234
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done
ma invece vedo
PID: 1234
OUT: 0
Done
Capisco che questo abbia a che fare con il buffering: se cambio quel comando in qualcosa di simile
# The $|++ disables buffering ./run perl -E '$|++; while(1) { state $i; say $i++; sleep 1 }'
Ottengo l'output desiderato.
So che gli oggetti TTY IO :: Handle sono senza buffer e che in questo caso il $*OUT
processo generato non è uno. E ho letto che gli oggetti IO :: Pipe sono bufferizzati "in modo che una scrittura senza una lettura non si blocchi immediatamente" (anche se non posso dire di aver capito completamente cosa significa).
Ma non importa cosa ho provato, non riesco a ottenere il flusso di output senza buffer di Proc :: Async. Come faccio a fare questo?
Ho provato ad associare un IO :: Handle aperto usando $proc.bind-stdout
ma continuo a riscontrare lo stesso problema.
Nota che fare qualcosa di simile $proc.bind-stdout: $*OUT
funziona, nel senso che l'oggetto Proc :: Async non bufferizza più, ma non è nemmeno una soluzione al mio problema, perché non posso attingere all'output prima che si spenga. Mi suggerisce che se posso associare Proc :: Async a un handle senza buffer, dovrebbe fare la cosa giusta. Ma non sono nemmeno riuscito a farlo funzionare.
Per chiarimenti: come suggerito con l'esempio Perl, so di poter risolvere questo problema disabilitando il buffering sul comando che passerò come input, ma sto cercando un modo per farlo dal lato che crea il Proc: : Oggetto asincrono.
Risposte
Proc::Async
di per sé non esegue il buffering sui dati ricevuti. Tuttavia, i processi generati possono fare da soli a seconda di ciò a cui stanno emettendo, ed è ciò che viene osservato qui.
Molti programmi prendono decisioni sul buffer di output (tra le altre cose, come se emettere codici colore) in base al fatto che l'handle di output sia collegato a un TTY (un terminale). Il presupposto è che un TTY significhi che un essere umano guarderà l'output, quindi la latenza è preferibile al throughput, quindi il buffering è disabilitato (o limitato al buffer di linea). Se, d'altra parte, l'output va in una pipe o in un file, allora il presupposto è che la latenza non è così importante e il buffering viene utilizzato per ottenere un throughput significativo (molte meno chiamate di sistema per scrivere dati).
Quando generiamo qualcosa con Proc::Async
, l'output standard del processo generato è associato a un pipe, che non è un TTY. Quindi il programma invocato può usarlo per decidere di applicare il buffering dell'output.
Se sei disposto ad avere un'altra dipendenza, puoi richiamare il programma tramite. qualcosa che falsifica un TTY, come unbuffer
(parte del expect
pacchetto, a quanto pare). Ecco un esempio di un programma che soffre di buffering:
my $proc = Proc::Async.new: 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
.print
}
Vediamo solo un 0
e quindi dobbiamo aspettare molto tempo per più output. Eseguendolo tramite unbuffer
:
my $proc = Proc::Async.new: 'unbuffer', 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
.print
}
Significa che vediamo un numero di output ogni secondo.
Raku potrebbe fornire una soluzione integrata a questo un giorno? Sì, facendo la "magia" che unbuffer
fa da sé (presumo di allocare una pty
specie di falso TTY). Questo non è banale, sebbene sia stato esplorato dagli sviluppatori di libuv ; almeno per quanto riguarda Rakudo su MoarVM, nel momento in cui sarà disponibile una versione di libuv che offre tale funzionalità, lavoreremo per esporla.
Puoi impostare il valore .out-buffer
di una maniglia (come $*OUT
o $*ERR
) su 0:
$ ./run raku -e '$*OUT.out-buffer = 0; react whenever Supply.interval: 1 { .say }'
PID: 11340
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done