14

用代码最容易解释:

require 'timeout'

puts "this block will properly kill the sleep after a second"

IO.popen("sleep 60") do |io|
  begin
    Timeout.timeout(1) do
      while (line=io.gets) do
        output += line
      end
    end
  rescue Timeout::Error => ex
    Process.kill 9, io.pid
    puts "timed out: this block worked correctly"
  end
end

puts "but this one blocks for >1 minute"

begin
  pid = 0
  Timeout.timeout(1) do
    IO.popen("sleep 60") do |io|
      pid = io.pid
      while (line=io.gets) do
        output += line
      end
    end
  end
rescue Timeout::Error => ex
  puts "timed out: the exception gets thrown, but much too late"
end

我对这两个块的心智模型是相同的:

流程图

那么,我错过了什么?

编辑:drmaciver 在推特上建议,在第一种情况下,由于某种原因,管道套接字进入非阻塞模式,但在第二种情况下却没有。我想不出为什么会发生这种情况,也无法弄清楚如何获取描述符的标志,但这至少是一个合理的答案?致力于这种可能性。

4

2 回答 2

16

啊哈,微妙。

ensure在第二种情况下,IO#popen 块的末尾有一个隐藏的阻塞子句。Timeout::Error及时引发的,但rescue在执行从该隐式ensure子句返回之前,您不能这样做。

在引擎盖下IO.popen(cmd) { |io| ... }做这样的事情:

def my_illustrative_io_popen(cmd, &block)
  begin
    pio = IO.popen(cmd)
    block.call(pio)      # This *is* interrupted...
  ensure
    pio.close            # ...but then control goes here, which blocks on cmd's termination
  end

并且 IO#close 调用实际上或多或少是 a pclose(3),它会阻止你waitpid(2)直到睡觉的孩子退出。

您可以像这样验证这一点:

#!/usr/bin/env ruby

require 'timeout'

BEGIN { $BASETIME = Time.now.to_i }

def xputs(msg)
  puts "%4.2f: %s" % [(Time.now.to_f - $BASETIME), msg]
end

begin
  Timeout.timeout(3) do
    begin
      xputs "popen(sleep 10)"
      pio = IO.popen("sleep 10")
      sleep 100                     # or loop over pio.gets or whatever
    ensure
      xputs "Entering ensure block"
      #Process.kill 9, pio.pid      # <--- This would solve your problem!
      pio.close
      xputs "Leaving ensure block"
    end
  end
rescue Timeout::Error => ex
  xputs "rescuing: #{ex}"
end

所以,你可以做什么?

您必须以显式方式执行此操作,因为解释器没有公开覆盖 IO#popenensure逻辑的方法。例如,您可以使用上面的代码作为起始模板并取消注释该kill()行。

于 2013-06-21T17:29:01.440 回答
1

在第一个块中,在子节点中引发超时,将其杀死并将控制权返回给父节点。在第二个块中,超时在父级中提出。孩子永远得不到信号。

请参阅io.c https://github.com/ruby/ruby/blob/trunk/io.c#L6021timeout.rb https://github.com/ruby/ruby/blob/trunk/lib/timeout.rb#L51

于 2013-06-21T15:47:08.683 回答