【问题标题】:How to fix hanging popen3 in Ruby?如何修复在 Ruby 中挂起的 popen3?
【发布时间】:2012-02-15 15:30:27
【问题描述】:

我在使用 popen3 时遇到了意外行为,我想用它来运行类似 tool ala cmd < file1 > file2 这样的命令。下面的示例挂起,因此永远无法到达stdout done。使用cat 以外的其他工具可能会导致挂起,因此永远无法到达stdin done。我怀疑,我正在遭受缓冲,但我该如何解决这个问题?

#!/usr/bin/env ruby

require 'open3'

Open3.popen3("cat") do |stdin, stdout, stderr, wait_thr|
  stdin.puts "foobar"

  puts "stdin done"

  stdout.each_line { |line| puts line }

  puts "stdout done"

  puts wait_thr.value
end

puts "all done"

【问题讨论】:

  • 我添加了popen3标签。

标签: ruby popen3


【解决方案1】:

stdout.each_line 正在等待来自cat 的进一步输出,因为cat 的输出流仍处于打开状态。它仍然打开,因为 cat 仍在等待用户输入,因为它的输入流尚未关闭(您会注意到,当您在终端中打开 cat 并输入 foobar 时,它仍然会正在运行并等待输入,直到您按 ^d 关闭流)。

因此,要解决此问题,只需在打印输出之前调用 stdin.close

【讨论】:

  • 谢谢。我确实对此进行了测试,并且在此示例中效果很好。然而,在我的现实生活中它仍然挂起,但这也是使用线程(并且使用临时文件而不是 popen3 工作正常)。也许 popen3 不是线程安全的?
  • 此修复仅在 stdin.puts 的参数很短时有效。如果您向与变量stdin 关联的管道写入更多数据,cat 将写入管道的缓冲区以用于 stdout,直到缓冲区已满。此时,操作系统将暂停cat。如果在这种情况下向stdin 写入更多数据,则会填充标准输入管道的缓冲区。当此管道已满时,调用 stdin.puts 阻塞。这意味着:您的程序挂起。因此,如果您有很多数据,则必须交替写入stdin 和读取stdout(和stderr)。为什么不使用Open3.capture*
  • @maasha popen3 是线程安全的。尽管您需要深入了解进程间通信才能在现实生活场景中正确使用它。
【解决方案2】:

您的代码已挂起,因为stdin 仍处于打开状态!

如果您使用popen3,则需要使用IO#closeIO#close_write 将其关闭。

如果你使用popen,那么你需要使用IO#close_write,因为它只使用一个文件描述符。

 #!/usr/bin/env ruby
 require 'open3'

 Open3.popen3("cat") do |stdin, stdout, stderr, wait_thr|
   stdin.puts "foobar"

   stdin.close   # close stdin like this!  or with stdin.close_write

   stdout.each_line { |line| puts line }

   puts wait_thr.value
 end

另见:

Ruby 1.8.7 IO#close_write

Ruby 1.9.2 IO#close_write

Ruby 2.3.1 IO#close_write

【讨论】:

  • 当然,这取决于您要通过管道传输的进程是否通过标准输入的 EOT 写入标准输出。如果进程在收到的每一行都写入标准输出,那么可能是其他原因导致挂起。
【解决方案3】:

Tilo 和 sepp2k 的答案是正确的:如果您关闭 stdin,您的简单测试将结束。问题解决了。

尽管在您对answer of sepp2k 的评论中,您表示您仍然遇到挂起。 嗯,有一些你可能忽略了的陷阱。

卡在标准错误的完整缓冲区

如果您调用的程序打印到 stderr 的数量超过了匿名管道的缓冲区可以容纳的数量(当前 Linux 为 64KiB),则该程序将被挂起。挂起的程序既不退出也不关闭标准输出。因此,从其标准输出读取将挂起。因此,如果您想正确执行,则必须使用线程或IO.select、非阻塞、无缓冲读取,以便并行或轮流从 stdout 和 stderr 读取而不会卡住。

卡在标准输入的完整缓冲区

如果您尝试向您的程序 (cat) 提供比“foobar”更多(更多)的内容,则 stdout 的匿名管道缓冲区将被填满。操作系统将暂停cat。如果您向 stdin 写入更多内容,则 stdin 的匿名管道的缓冲区将变满。那么您对stdin.write 的呼叫将被卡住。这意味着:您需要并行或轮流写入标准输入、从标准输出读取和从标准错误读取。

结论

阅读一本好书(Richards Stevens,“UNIX 网络编程:进程间通信”)并使用好的库函数。 IPC(进程间通信)太复杂了,容易出现不确定的运行时行为。尝试通过试错法来解决问题太麻烦了。

使用Open3.capture3

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-23
    • 2014-10-09
    • 2011-12-05
    • 1970-01-01
    • 2011-07-28
    • 1970-01-01
    • 2013-07-19
    相关资源
    最近更新 更多