Как мне перехватить небуферизованный вывод Proc :: Async в 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 не буферизованы , и что в этом случае $*OUT
порожденный процесс не один. И я читал, что объекты IO :: Pipe помещаются в буфер, «чтобы запись без чтения не блокировалась немедленно» (хотя я не могу сказать, что полностью понимаю, что это означает).
Но что бы я ни пробовал, я не могу получить небуферизованный выходной поток Proc :: Async. Как мне это сделать?
Я пробовал связать открытый IO :: Handle с помощью, $proc.bind-stdout
но у меня все еще та же проблема.
Обратите внимание, что выполнение чего-то вроде $proc.bind-stdout: $*OUT
действительно работает в том смысле, что объект Proc :: Async больше не буферизуется, но это также не решение моей проблемы, потому что я не могу подключиться к выходу до того, как он исчезнет. Это подсказывает мне, что если я смогу привязать Proc :: Async к небуферизованному дескриптору, он должен поступить правильно. Но я тоже не смог заставить это работать.
Для пояснения: как было предложено в примере Perl, я знаю, что могу исправить это, отключив буферизацию в команде, которую я буду передавать в качестве входных данных, но я ищу способ сделать это со стороны, которая создает Proc: : Асинхронный объект.
Ответы
Proc::Async
сам не выполняет буферизацию полученных данных. Однако порожденные процессы могут действовать самостоятельно в зависимости от того, что они выводят, и это то, что здесь наблюдается.
Многие программы принимают решения о своей буферизации вывода (среди прочего, например, следует ли выдавать цветовые коды) на основе того, прикреплен ли дескриптор вывода к 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
}
Мы видим только a, 0
а затем вынуждены долго ждать дополнительных результатов. Запуск через unbuffer
:
my $proc = Proc::Async.new: 'unbuffer', 'raku', '-e', 'react whenever Supply.interval(1) { .say }'; react whenever $proc.stdout {
.print
}
Означает, что мы видим вывод числа каждую секунду.
Может ли Raku когда-нибудь предоставить встроенное решение для этого? Да, выполняя «магию», которая unbuffer
сама делает (я предполагаю, что это pty
своего рода поддельный телетайп). Это нетривиально - хотя разработчики libuv исследуют его ; по крайней мере, что касается Rakudo на MoarVM, как только появится выпуск libuv, предлагающий такую функцию, мы будем работать над ее раскрытием.
Вы можете установить .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