Рубин Разбитая труба @ io_write - <STDOUT> - PullRequest
2 голосов
/ 24 сентября 2019

при попытке запустить программу ruby ​​и передать вывод в другую программу, например так:

ruby hello.rb | whoami

Команда whoami выполняется сначала, как и ожидалось, но после этого hello.rb вылетает с:

Traceback (most recent call last):
    2: from p.rb:2:in `<main>'
    1: from p.rb:2:in `print'
p.rb:2:in `write': Broken pipe @ io_write - <STDOUT> (Errno::EPIPE)

Это происходит, только если STDOUT.sync установлено на true

STDOUT.sync = true
STDOUT.print "Hello!"

[и аналогичная ошибка возникает с STDOUT.flush после STDOUT.puts при передаче по каналу другомупрограмма]

В чем причина этого сбоя?

1 Ответ

1 голос
/ 25 сентября 2019

Введение

Во-первых, объяснение можно найти здесь .

В любом случае, вот моя мысль ...

Когда используется трубакак это:

a | b

И a, и b выполняются одновременно .b ожидает стандартного ввода от.

Говоря о Errno::EPIPE, справочная страница Linux с записью говорит:

EPIPE fd подключен к каналуили сокет, конец чтения которого закрыт.Когда это происходит, процесс записи также получит сигнал SIGPIPE.(Таким образом, возвращаемое значение записи отображается только в том случае, если программа перехватывает, блокирует или игнорирует этот сигнал.)

Говоря о проблеме в вопросе: при запуске программы whoami она завершаетсяи больше не принимает стандартные входные данные, которые отправляет программа ruby ​​hello.rb, что приводит к разрыву канала.

Здесь я написал 2 программы ruby ​​с именами p.rb и q.rb, чтобы проверить, что:

  • p.rb
#!/usr/bin/env ruby
print ?* * 100_000
  • q.rb
#!/usr/bin/ruby
exit! 0

Бег:

bash[~] $ ruby p.rb | ruby q.rb

Traceback (most recent call last):
    2: from p.rb:2:in `<main>'
    1: from p.rb:2:in `print'
p.rb:2:in `write': Broken pipe @ io_write - <STDOUT> (Errno::EPIPE)

Давайте немного изменим код q.rb , чтобы он принимал входные данные:

#!/usr/bin/ruby -w
STDIN.gets

Запуск:

bash[~] $ ruby p.rb | ruby q.rb

Правильно, на самом деле ничего не отображается.Причина в том, что q.rb теперь ждет стандартных вводов. По-видимому, здесь важнее всего ожидание .Теперь p.rb не будет зависать даже с STDOUT.sync или STDOUT.flush при передаче по этому q.rb.

Другой пример:

  • p.rb
STDOUT.sync = true
loop until print("\e[2K<<<#{Time.now.strftime('%H:%M:%S:%2N')}>>>\r")

[предупреждение: цикл без сна может увеличить загрузку вашего процессора]

  • q.rb
sleep 3

Запуск:

bash[~] $ time ruby p.rb | q.rb
Traceback (most recent call last):
    2: from p.rb:2:in `<main>'
    1: from p.rb:2:in `print'
p.rb:2:in `write': Broken pipe @ io_write - <STDOUT> (Errno::EPIPE)

real    0m3.186s
user    0m0.282s
sys 0m0.083s

Вы видите, что программа потерпела крах через 3 секунды.Он потерпит крах через 5,1 секунды, если у q.rb было sleep 5.Точно так же sleep 0 in q.rb завершится сбоем p.rb через 0,1 секунды.Я предполагаю, что дополнительные 0,1 секунды зависят от системы, потому что моей системе требуется 0,1 секунды для загрузки интерпретатора ruby.

Я написал программы Crystal для p.cr и q.cr для тестирования.Crystal скомпилирован и не загружается долго 0,1 секунды.

Программы Crystal:

  • p.cr
STDOUT.sync = true
loop do print("\e[2KHi!\r") end rescue exit
  • q.cr
sleep 3

Я скомпилировал их и запустил:

bash[~] $ time ./p | ./q

real    0m3.013s
user    0m0.007s
sys 0m0.019s

Двоичный файл . / P , очень близко к 3 секундам, обрабатывает Unhandled exception: Error writing file: Broken pipe (Errno) и завершает работу.Опять же, для выполнения двух кристаллических программ может потребоваться 0,01 секунды, и, возможно, ядру также требуется немного времени для запуска процессов.

Также обратите внимание, что STDERR#print, STDERR#puts, STDERR#putc, STDERR#printf, STDERR#write, STDERR#syswrite не вызывает Errno :: EPIPE, даже если выход синхронизирован.

Заключение

Труба arcane .Установка STDOUT#sync в значение true или использование STDOUT#flush сбрасывает все буферизованные данные в базовую операционную систему.

При запуске hello.rb | whoami без синхронизации я могу записать 8191 байт данных, а программа hello.rb не вылетает.Но с синхронизацией запись 1 байта через канал приведет к сбою hello.rb .

Так что, когда hello.rb синхронизирует стандартные выходные данные с переданной по каналу программой whoami, иwhoami не ждет hello.rb ; hello.rb повышает Errno::EPIPE, потому что канал между этими двумя программами сломан (поправьте меня, если я здесь потерялся).

...