¿Cómo intercepto la salida sin búfer de un Proc :: Async en Raku?

Aug 19 2020

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 $*OUTproceso 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-stdoutpero sigo teniendo el mismo problema.

Tenga en cuenta que hacer algo como $proc.bind-stdout: $*OUTfunciona, 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

3 JonathanWorthington Aug 21 2020 at 05:13

Proc::Asyncen 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( expectparece 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 0y 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 unbufferhace (supongo que asigna una ptyespecie 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.

6 ugexe Aug 19 2020 at 20:11

Puede establecer el .out-bufferde un identificador (como $*OUTo $*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