如何拦截 Raku 中 Proc::Async 的无缓冲输出?

How do I intercept the unbuffered output of a Proc::Async in Raku?

像这样的片段

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

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

我希望看到类似的东西

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

但我看到了

PID: 1234
OUT: 0
Done

我知道这与缓冲有关:如果我将该命令更改为

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

我得到了想要的输出。

我知道 TTY IO::Handle objects are unbuffered, and that in this case the $*OUT of the spawned process is not one. And I've read that IO::Pipe 对象 缓冲的“这样没有读取的写入不会立即阻塞”(虽然我不能说我完全理解这是什么意思是).

但是无论我尝试过什么,我都无法获得 Proc::Async 的无缓冲输出流。我该怎么做?

我已经尝试使用 $proc.bind-stdout 绑定一个开放的 IO::Handle,但我仍然遇到同样的问题。

请注意,$proc.bind-stdout: $*OUT 之类的操作确实有效,因为 Proc::Async 对象不再缓冲,但这也不是我的问题的解决方案,因为我无法利用输出在它熄灭之前。它确实向我建议,如果我可以将 Proc::Async 绑定到一个无缓冲的句柄,它应该做正确的事情。但我也没能让它发挥作用。


为了澄清:正如 Perl 示例所建议的,我知道我可以通过禁用我将作为输入传递的命令的缓冲来解决这个问题,但我正在寻找一种从侧面执行此操作的方法创建 Proc::Async 对象。

您可以将句柄(例如$*OUT$*ERR)的.out-buffer设置为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

Proc::Async 本身不对接收到的数据执行缓冲。但是,生成的进程可能会根据它们的输出目标执行自己的操作,这就是此处观察到的情况。

许多程序根据输出句柄是否附加到 TTY(终端)来决定其输出缓冲(除其他事项外,例如是否发出颜色代码)。假设 TTY 意味着人类将要观察输出,因此延迟比吞吐量更可取,因此缓冲被禁用(或仅限于线路缓冲)。另一方面,如果输出到管道或文件,则假设延迟不是那么重要,缓冲用于实现显着的吞吐量胜利(写入数据的系统调用少得多)。

当我们使用 Proc::Async 产生一些东西时,产生的进程的标准输出被绑定到一个管道 - 这不是 TTY。因此被调用的程序可以使用它来决定应用输出缓冲。

如果你愿意有另一个依赖,那么你可以通过调用程序。伪造 TTY 的东西,例如 unbuffer(似乎是 expect 包的一部分)。这是一个遭受缓冲的程序示例:

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

我们只看到一个0,然后要等待很长时间才能有更多的输出。 运行 通过 unbuffer:

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

意味着我们每秒看到一个数字输出。

Raku 能否在某一天提供 built-in 解决方案?是的——通过做 unbuffer 本身做的“魔法”(我假设分配一个 pty——一种假的 TTY)。这不是微不足道的——尽管它是 explored by the libuv developers;至少就 MoarVM 上的 Rakudo 而言,一旦有提供此类功能的 libuv 版本可用,我们将努力公开它。