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

jja*_*jja 8 stdout buffering 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 Code Online (Sandbox Code Playgroud)

./run raku -e 'react whenever Supply.interval: 1 { .say }'
Run Code Online (Sandbox Code Playgroud)

我希望看到类似的东西

PID: 1234
OUT: 0
OUT: 1
OUT: 2
OUT: 3
OUT: 4
Done
Run Code Online (Sandbox Code Playgroud)

但我看到

PID: 1234
OUT: 0
Done
Run Code Online (Sandbox Code Playgroud)

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

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

我得到了想要的输出。

我知道TTY IO::Handle 对象是 unbuffered,在这种情况下$*OUT,产生的进程不是一个。而且我已经读过IO::Pipe对象缓冲“以便没有读取的写入不会立即阻塞”(尽管我不能说我完全理解这意味着什么)。

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

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

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


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

uge*_*exe 6

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


Jon*_*ton 6

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
}
Run Code Online (Sandbox Code Playgroud)

我们只看到一个0,然后必须等待很长时间才能获得更多输出。通过以下方式运行它unbuffer

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

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

Raku 有一天可以为此提供内置解决方案吗?是的 - 通过做它本身所做的“魔法” unbuffer(我假设分配一个pty- 一种假的 TTY)。这并不是一件小事——尽管libuv 开发人员正在探索它;至少就 MoarVM 上的 Rakudo 而言,一旦有提供此类功能的 libuv 版本可用,我们将致力于公开它。