【问题标题】:Ruby Broken pipe @ io_write - <STDOUT>Ruby Broken pipe @ io_write - <STDOUT>
【发布时间】:2020-01-24 17:20:23
【问题描述】:

在尝试运行 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.flushSTDOUT.puts 之后会引发类似的错误]

这次崩溃的原因是什么?

【问题讨论】:

  • 问题是 whoami 正在写入 STDOUT,并且通过刷新缓冲区,您的 ruby​​ 脚本现在正在写入损坏的管道。这是关于这个问题的一篇很好的文章:stackoverflow.com/a/30103307/1954610

标签: ruby pipe stdout


【解决方案1】:

简介

首先可以找到解释here

无论如何,这是我的想法......

当这样使用管道时:

a | b

a 和 b 都执行concurrently。 b 等待来自 a 的标准输入。

说到Errno::EPIPELinux man page of write 说:

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 现在等待标准输入。 显然,这里最重要的是等待。现在,即使使用STDOUT.syncSTDOUT.flush 传送到此 q.rb,p.rb 也不会崩溃。

另一个例子:

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

[警告:没有睡眠的循环可能会增加你的 CPU 使用率]

  • 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 秒后崩溃。如果 q.rb 有sleep 5,它将在 5.1 秒后崩溃。同样,q.rb 中的sleep 0 会在 0.1 秒后崩溃 p.rb。我猜额外的 0.1 秒取决于系统,因为我的系统需要 0.1 秒来加载 ruby​​ 解释器。

我编写了 p.cr 和 q.cr Crystal 程序进行测试。 Crystal 是 compiled,加载时间不长,需要 0.1 秒。

水晶计划:

  • 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#printSTDERR#putsSTDERR#putcSTDERR#printfSTDERR#writeSTDERR#syswrite 不会引发 Errno::EPIPE,即使输出是同步的。

结论

管道是arcane。将STDOUT#sync 设置为true 或使用STDOUT#flush 会将所有缓冲数据刷新到底层操作系统。

在运行hello.rb | whoami时,没有同步,我可以写入8191字节的数据,并且程序hello.rb没有崩溃。但是使用同步,通过管道写入 1 个字节会崩溃 hello.rb

所以当 hello.rb 将标准输出与管道程序 whoami 同步时,whoami 不会等待 hello.rbhello.rb 引发 Errno::EPIPE,因为这两个程序之间的管道已损坏(如果我在这里迷路了,请纠正我)。

【讨论】:

    猜你喜欢
    • 2023-03-30
    • 2012-06-14
    • 2016-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多