Comment intercepter la sortie sans tampon d'un Proc :: Async dans Raku?
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 $*OUT
processus 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-stdout
mais 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
Proc::Async
lui-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 expect
paquet, 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 0
et 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 unbuffer
lui - 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.
Vous pouvez définir .out-buffer
une poignée (telle que $*OUT
ou $*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