RakuでProc :: Asyncのバッファリングされていない出力をインターセプトするにはどうすればよいですか?

Aug 19 2020

次のようなスニペットを使用

# 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オブジェクトはバッファリングされて$*OUTおらず、この場合、生成されたプロセスは1つではないことを私は知っています。そして、IO :: Pipeオブジェクトバッファリングされていることを読みました。これにより、「読み取りなしの書き込みがすぐにブロックされないように」なります(ただし、これが何を意味するのかを完全に理解しているとは言えません)。

しかし、何を試しても、Proc :: Asyncのバッファリングされていない出力ストリームを取得できません。どうすればよいですか?

を使用してオープンIO :: Handleをバインドしようとしました$proc.bind-stdoutが、それでも同じ問題が発生します。

$proc.bind-stdout: $*OUTProc :: Asyncオブジェクトがバッファリングしなくなったという意味で、次のようなことを行うことは機能しますが、出力が出力される前に利用できないため、問題の解決策でもないことに注意してください。Proc :: Asyncをバッファリングされていないハンドルにバインドできれば、それは正しいことをするはずだと私には示唆しています。しかし、私もそれを機能させることができませんでした。


明確にするために:Perlの例で示唆されているように、入力として渡すコマンドのバッファリングを無効にすることでこれを修正できることはわかっていますが、Procを作成する側からこれを行う方法を探しています: :非同期オブジェクト。

回答

3 JonathanWorthington Aug 21 2020 at 05:13

Proc::Asyncそれ自体は、受信したデータに対してバッファリングを実行していません。ただし、生成されたプロセスは、出力先によっては独自のプロセスを実行する場合があり、それがここで観察されています。

多くのプログラムは、出力ハンドルがTTY(端末)に接続されているかどうかに基づいて、出力バッファリング(特にカラーコードを出力するかどうかなど)について決定を下します。TTYは、人間が出力を監視することを意味するため、スループットよりもレイテンシが望ましいため、バッファリングが無効になります(またはラインバッファリングに制限されます)。一方、出力がパイプまたはファイルに送られる場合は、レイテンシーはそれほど重要ではないと想定され、バッファリングを使用してスループットを大幅に向上させます(データを書き込むためのシステムコールが大幅に少なくなります)。

で何かをスポーンするとProc::Async、スポーンされたプロセスの標準出力はパイプにバインドされます。これはTTYではありません。したがって、呼び出されたプログラムはこれを使用して、出力バッファリングを適用することを決定できます。

別の依存関係を希望する場合は、を介してプログラムを呼び出すことができます。unbufferexpectパッケージの一部のようです)など、TTYを偽造するもの。バッファリングに悩まされているプログラムの例を次に示します。

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
}

毎秒数値が出力されることを意味します。

ラクはいつかこれに組み込みのソリューションを提供できるでしょうか?はい-unbufferそれ自体が行う「魔法」を実行することによって(私はpty-一種の偽のTTYを割り当てると思います)。これは些細なことではありません-libuv開発者によって調査されていますが; 少なくともMoarVMのRakudoに関する限り、そのような機能を提供するlibuvリリースが利用可能になったら、それを公開する作業を行います。

6 ugexe Aug 19 2020 at 20:11

.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