¿Cómo intercepto la salida sin búfer de un Proc :: Async en Raku?
Con un fragmento como
# 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" }
}
ejecutado como
./run raku -e 'react whenever Supply.interval: 1 { .say }'
Esperaba ver algo como
PID: 1234
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done
pero en cambio veo
PID: 1234
OUT: 0
Done
Entiendo que esto tiene que ver con el almacenamiento en búfer: si cambio ese comando a algo como
# The $|++ disables buffering ./run perl -E '$|++; while(1) { state $i; say $i++; sleep 1 }'
Obtengo la salida deseada.
Sé que los objetos TTY IO :: Handle no tienen búfer y que, en este caso, el $*OUT
proceso generado no es uno. Y he leído que los objetos IO :: Pipe se almacenan en búfer "para que una escritura sin una lectura no se bloquee inmediatamente" (aunque no puedo decir que entiendo completamente lo que esto significa).
Pero no importa lo que haya intentado, no puedo obtener el flujo de salida sin búfer de un Proc :: Async. ¿Cómo hago esto?
Intenté vincular un IO :: Handle abierto usando $proc.bind-stdout
pero sigo teniendo el mismo problema.
Tenga en cuenta que hacer algo como $proc.bind-stdout: $*OUT
funciona, en el sentido de que el objeto Proc :: Async ya no se almacena en búfer, pero tampoco es una solución a mi problema, porque no puedo acceder a la salida antes de que se apague. Me sugiere que si puedo enlazar Proc :: Async a un identificador sin búfer, debería hacer lo correcto. Pero tampoco he podido hacer que eso funcione.
Para aclarar: como se sugiere con el ejemplo de Perl, sé que puedo solucionar esto desactivando el almacenamiento en búfer en el comando que pasaré como entrada, pero estoy buscando una manera de hacerlo desde el lado que crea el Proc: : Objeto asincrónico.
Respuestas
Proc::Async
en sí mismo no está realizando el almacenamiento en búfer de los datos recibidos. Sin embargo, los procesos generados pueden hacer lo suyo dependiendo de lo que estén generando, y eso es lo que se observa aquí.
Muchos programas toman decisiones sobre el almacenamiento en búfer de salida (entre otras cosas, como emitir códigos de color) basándose en si el controlador de salida está conectado a un TTY (un terminal). La suposición es que un TTY significa que un humano va a estar mirando la salida y, por lo tanto, la latencia es preferible al rendimiento, por lo que el almacenamiento en búfer está deshabilitado (o restringido al búfer de línea). Si, por otro lado, la salida va a una tubería o un archivo, entonces se supone que la latencia no es tan importante y el almacenamiento en búfer se utiliza para lograr una ganancia de rendimiento significativa (muchas menos llamadas al sistema para escribir datos).
Cuando generamos algo con Proc::Async
, la salida estándar del proceso generado está vinculada a una tubería, que no es un TTY. Por tanto, el programa invocado puede utilizar esto para decidir aplicar el almacenamiento en búfer de salida.
Si está dispuesto a tener otra dependencia, puede invocar el programa a través de. algo que simula un TTY, como unbuffer
( expect
parece ser parte del paquete). Aquí hay un ejemplo de un programa que sufre de almacenamiento en búfer:
my $proc = Proc::Async.new: 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
.print
}
Solo vemos un 0
y luego tenemos que esperar mucho tiempo para obtener más resultados. Ejecutarlo a través de unbuffer
:
my $proc = Proc::Async.new: 'unbuffer', 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
.print
}
Significa que vemos un número de salida cada segundo.
¿Podría Raku proporcionar una solución incorporada a esto algún día? Sí, haciendo la "magia" que unbuffer
hace (supongo que asigna una pty
especie de TTY falso). Esto no es trivial, aunque está siendo explorado por los desarrolladores de libuv ; al menos en lo que respecta a Rakudo en MoarVM, en el momento en que haya una versión de libuv disponible que ofrezca tal característica, trabajaremos para exponerla.
Puede establecer el .out-buffer
de un identificador (como $*OUT
o $*ERR
) en 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