Ruby 管道损坏 @ io_write - <STDOUT>
Ruby Broken pipe @ io_write - <STDOUT>
同时尝试 运行 一个 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.puts
之后通过管道传输到另一个程序时 STDOUT.flush
引发了类似的错误]
这次崩溃的原因是什么?
简介
首先,可以找到一个解释here。
无论如何,这是我的想法...
当这样使用管道时:
a | b
a和b都执行了concurrently。 b 等待来自 a 的标准输入。
说到Errno::EPIPE
,Linux man page of write说:
EPIPE fd is connected to a pipe or socket whose reading end is
closed. When this happens the writing process will also
receive a SIGPIPE signal. (Thus, the write return value is
seen only if the program catches, blocks or ignores this
signal.)
说到题中的问题:
当程序 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.sync
或 STDOUT.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.crCrystal程序来测试。 Crystal 是 compiled 并且不需要 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)
并退出。同样,两个 crystal 程序可能需要 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
因为这两个程序之间的管道坏了(如果我在这里迷路了请纠正我)。
同时尝试 运行 一个 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.puts
之后通过管道传输到另一个程序时 STDOUT.flush
引发了类似的错误]
这次崩溃的原因是什么?
简介
首先,可以找到一个解释here。
无论如何,这是我的想法...
当这样使用管道时:
a | b
a和b都执行了concurrently。 b 等待来自 a 的标准输入。
说到Errno::EPIPE
,Linux man page of write说:
EPIPE fd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIGPIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.)
说到题中的问题:
当程序 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.sync
或 STDOUT.flush
传送到此 q.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.crCrystal程序来测试。 Crystal 是 compiled 并且不需要 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)
并退出。同样,两个 crystal 程序可能需要 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
因为这两个程序之间的管道坏了(如果我在这里迷路了请纠正我)。