Как мне перехватить небуферизованный вывод Proc :: Async в Raku?

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порожденный процесс не один. И я читал, что объекты IO :: Pipe помещаются в буфер, «чтобы запись без чтения не блокировалась немедленно» (хотя я не могу сказать, что полностью понимаю, что это означает).

Но что бы я ни пробовал, я не могу получить небуферизованный выходной поток Proc :: Async. Как мне это сделать?

Я пробовал связать открытый IO :: Handle с помощью, $proc.bind-stdoutно у меня все еще та же проблема.

Обратите внимание, что выполнение чего-то вроде $proc.bind-stdout: $*OUTдействительно работает в том смысле, что объект Proc :: Async больше не буферизуется, но это также не решение моей проблемы, потому что я не могу подключиться к выходу до того, как он исчезнет. Это подсказывает мне, что если я смогу привязать Proc :: Async к небуферизованному дескриптору, он должен поступить правильно. Но я тоже не смог заставить это работать.


Для пояснения: как было предложено в примере Perl, я знаю, что могу исправить это, отключив буферизацию в команде, которую я буду передавать в качестве входных данных, но я ищу способ сделать это со стороны, которая создает Proc: : Асинхронный объект.

Ответы

3 JonathanWorthington Aug 21 2020 at 05:13

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, предлагающий такую ​​функцию, мы будем работать над ее раскрытием.

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