Come intercetto l'output senza buffer di un Proc :: Async in Raku?

Aug 19 2020

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 $*OUTprocesso 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-stdoutma continuo a riscontrare lo stesso problema.

Nota che fare qualcosa di simile $proc.bind-stdout: $*OUTfunziona, 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

3 JonathanWorthington Aug 21 2020 at 05:13

Proc::Asyncdi 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 expectpacchetto, 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 0e 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 unbufferfa da sé (presumo di allocare una ptyspecie 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.

6 ugexe Aug 19 2020 at 20:11

Puoi impostare il valore .out-bufferdi una maniglia (come $*OUTo $*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