Comment intercepter la sortie sans tampon d'un Proc :: Async dans Raku?

Aug 19 2020

Avec un extrait comme

# 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"            }
}

exécuté comme

./run raku -e 'react whenever Supply.interval: 1 { .say }'

Je m'attendais à voir quelque chose comme

PID: 1234
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done

mais à la place je vois

PID: 1234
OUT: 0
Done

Je comprends que cela a à voir avec la mise en mémoire tampon: si je change cette commande en quelque chose comme

# The $|++ disables buffering ./run perl -E '$|++; while(1) { state $i; say $i++; sleep 1 }'

J'obtiens la sortie souhaitée.

Je sais que les objets TTY IO :: Handle ne sont pas tamponnés et que dans ce cas, le $*OUTprocessus engendré n'en est pas un. Et j'ai lu que les objets IO :: Pipe sont mis en mémoire tampon "pour qu'une écriture sans lecture ne bloque pas immédiatement" (bien que je ne puisse pas dire que je comprends tout à fait ce que cela signifie).

Mais peu importe ce que j'ai essayé, je ne peux pas obtenir le flux de sortie sans tampon d'un Proc :: Async. Comment puis-je faire cela?

J'ai essayé de lier un IO :: Handle ouvert en utilisant $proc.bind-stdoutmais j'obtiens toujours le même problème.

Notez que faire quelque chose comme $proc.bind-stdout: $*OUTça fonctionne, dans le sens où l'objet Proc :: Async ne fait plus de tampons, mais ce n'est pas non plus une solution à mon problème, car je ne peux pas accéder à la sortie avant qu'elle ne s'éteigne. Cela me suggère que si je peux lier le Proc :: Async à un handle non tamponné, il devrait faire la bonne chose. Mais je n'ai pas réussi non plus à faire fonctionner cela.


Pour clarifier: comme suggéré avec l'exemple Perl, je sais que je peux résoudre ce problème en désactivant la mise en mémoire tampon sur la commande que je vais passer en entrée, mais je cherche un moyen de le faire du côté qui crée le Proc: : Objet asynchrone.

Réponses

3 JonathanWorthington Aug 21 2020 at 05:13

Proc::Asynclui-même n'effectue pas de mise en tampon sur les données reçues. Cependant, les processus générés peuvent faire leurs propres en fonction de ce qu'ils produisent, et c'est ce qui est observé ici.

De nombreux programmes prennent des décisions concernant leur mise en mémoire tampon de sortie (entre autres, comme l'émission de codes de couleur) en fonction du fait que la poignée de sortie est attachée à un TTY (un terminal). L'hypothèse est qu'un TTY signifie qu'un humain va regarder la sortie, et donc la latence est préférable au débit, donc la mise en mémoire tampon est désactivée (ou limitée à la mise en mémoire tampon de ligne). Si, d'un autre côté, la sortie est dirigée vers un tube ou un fichier, alors l'hypothèse est que la latence n'est pas si importante et que la mise en mémoire tampon est utilisée pour obtenir un gain de débit significatif (beaucoup moins d'appels système pour écrire des données).

Lorsque nous engendrons quelque chose avec Proc::Async, la sortie standard du processus généré est liée à un tube - qui n'est pas un TTY. Ainsi, le programme appelé peut l'utiliser pour décider d'appliquer la mise en mémoire tampon de sortie.

Si vous souhaitez avoir une autre dépendance, vous pouvez appeler le programme via. quelque chose qui simule un TTY, comme unbuffer(une partie du expectpaquet, semble-t-il). Voici un exemple de programme qui souffre de mise en mémoire tampon:

my $proc = Proc::Async.new: 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
    .print
}

Nous ne voyons qu'un 0et puis devons attendre longtemps pour plus de sortie. L'exécuter via unbuffer:

my $proc = Proc::Async.new: 'unbuffer', 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
    .print
}

Cela signifie que nous voyons un nombre sortir chaque seconde.

Raku pourrait-il fournir une solution intégrée à ce problème un jour? Oui - en faisant la "magie" qui unbufferlui - même fait (je présume attribuer une pty- sorte de faux TTY). Ce n'est pas anodin - bien que cela soit étudié par les développeurs de libuv ; du moins en ce qui concerne Rakudo sur MoarVM, dès qu'une version libuv est disponible offrant une telle fonctionnalité, nous travaillerons à l'exposer.

6 ugexe Aug 19 2020 at 20:11

Vous pouvez définir .out-bufferune poignée (telle que $*OUTou $*ERR) sur 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