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

2 つのブロックの私のメンタル モデルは同じです。

フローチャート

それで、私は何が欠けていますか?

編集: drmaciver は、最初のケースでは何らかの理由でパイプ ソケットが非ブロック モードになることを Twitter で提案しましたが、2 番目のケースではそうではありません。これが発生する理由は考えられませんし、記述子のフラグを取得する方法もわかりませんが、少なくとももっともらしい答えですか? その可能性に取り組んでいます。

4

2 に答える 2

16

あぁ、微妙。

ensure2 番目のケースでは、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

それで、あなたは何ができますか?

ensureインタープリターは IO#popenロジックをオーバーライドする方法を公開していないため、明示的な方法で行う必要があります。たとえば、上記のコードを開始テンプレートとして使用し、行のコメントを外すことができますkill()

于 2013-06-21T17:29:01.440 に答える
1

最初のブロックでは、タイムアウトが子で発生し、それを強制終了して親に制御を返します。2 番目のブロックでは、親でタイムアウトが発生します。子供は決して信号を受け取りません。

io.c https://github.com/ruby/ruby/blob/trunk/io.c#L6021 およびhttps://github.com/ruby/ruby/blob/trunk/lib/timeout.rb#L51を参照してください。timeout.rb

于 2013-06-21T15:47:08.683 に答える