2013-06-21 104 views
12

這是最簡單的代碼來解釋:在popen中超時工作,但在超時內popen不?

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 

我的兩個塊的心智模式是相同的:

flow chart

所以,我缺少什麼?

編輯:drmaciver在twitter上建議,在第一種情況下,由於某種原因,管道套接字進入非阻塞模式,但在第二種情況下不會。我想不出爲什麼會發生這種情況,我也不知道如何獲得描述符的標誌,但至少是一個合理的答案?在這個可能性上工作。

+0

你跑哪個紅寶石? –

+0

至少在1.8.7和1.9.3上發生此行爲。 jruby塊在這兩個塊上都是60,這是我先前猜測的行爲。 – llimllib

+0

請注意,您爲我的兩個塊之間的'puts(「但是這個......」)'等待第一個'sleep'完成,因爲第一個IO#popen塊正在對'waitpid() '。如果你不想這樣做,那麼你的救援邏輯就需要殺死子進程。 – pilcrow

回答

13

啊哈,含蓄。

在第二種情況下,存在隱藏的阻塞ensure子句,該子句位於IO#popen塊的末尾。超時::錯誤提出的適時提出的,但您不能rescue它直到從隱含的ensure子句執行返回。

Under the hoodIO.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#千鈞一髮真的是更多或更少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#popen ensure邏輯的方法。例如,您可以使用上面的代碼作爲起始模板,並取消對kill()行的註釋。

+0

我在io.c上盯着這麼久,看上去只有幾行*保證,沒有看到保證或考慮它。很好的回答,非常感謝。 – llimllib

+0

可以通過此解決方案獲取退出狀態嗎? – tiktak

0

在第一個塊中,超時在子級中引發,將其殺死並將控制權返回給父級。在第二個塊中,超時在父級中引發。孩子從來沒有得到信號。

io.chttps://github.com/ruby/ruby/blob/trunk/io.c#L6021timeout.rbhttps://github.com/ruby/ruby/blob/trunk/lib/timeout.rb#L51

+0

我知道這不是一個詳細的答案比爾,但這是我閱讀塊的方式。 –

+1

傳遞給IO#popen的塊在父進程的上下文中執行。當你說孩子的過程可能或不能「獲得」信號時,我不確定你的意思。 – pilcrow

+0

@JonathanJulian我已經在分屏中打開了這兩個文件,試圖找出它。據我所知,超時是從兩個例子中的主線程產生的。這裏是popen運行它傳遞的塊的地方:https://github.com/ruby/ruby/blob/trunk/io.c#L6075 – llimllib