2013-06-21 13 views
12

Najłatwiej wyjaśnić w kodzie:Limit czasu w popen działa, ale popen w limicie czasu nie działa?

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 

Moja umysłowy model dwóch bloków jest identyczna:

flow chart

Więc, co mi brakuje?

edytuj: drmaciver zasugerował na Twitterze, że w pierwszym przypadku z jakiegoś powodu gniazdo rury przechodzi w tryb bez blokowania, ale w drugim nie. Nie mogę wymyślić żadnego powodu, dla którego by to się stało, ani nie mogę wymyślić, jak zdobyć flagi deskryptora, ale jest to przynajmniej wiarygodna odpowiedź? Pracując nad tą możliwością.

+0

Jakiego rubinu używasz? –

+0

to zachowanie występuje co najmniej w wersjach 1.8.7 i 1.9.3. jorkie bloki dla wszystkich 60 na obu blokach, co jest zachowaniem, które bym odgadł a priori. – llimllib

+0

Zauważ, że twoje 'puts (" ale to ... ")' pomiędzy dwoma blokami dla mnie _waits aż do pierwszego 'sleep' jest complete_, ponieważ pierwszy blok popen IO # jest sumiennie w wywołaniu' waitpid() '. Jeśli tego nie chcesz, twoja logika ratunkowa musi zabić proces potomny. – pilcrow

Odpowiedz

13

Aha, subtelny.

Istnieje klauzula ukryta, blokująca ensure na końcu bloku popen IO # w drugim przypadku. Limit czasu :: Błąd to podniesiony podniesiony w odpowiednim czasie, ale nie można go wykonać, dopóki wykonanie nie powróci z domyślnej klauzuli ensure.

Under the hood, IO.popen(cmd) { |io| ... } robi coś takiego:

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 

i IO # blisko połączenie jest naprawdę bardziej lub mniej pclose(3), który blokuje cię waitpid(2) aż wyjść spanie dziecka.

Można to sprawdzić tak:

#!/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 

Więc co można zrobić?

Będziesz musiał to zrobić w sposób jawny, ponieważ tłumacz nie ujawnia sposobu na przesłonięcie logiki IO # popen ensure. Możesz użyć powyższego kodu jako szablonu początkowego i odkomentować na przykład linię kill().

+0

Patrzyłem tak długo w io.c, patrząc tylko kilka linii * powyżej * zapewnić, nie widząc zapewnienia lub biorąc pod uwagę to w ogóle. Świetna odpowiedź, wielkie dzięki. – llimllib

+0

Czy można uzyskać status wyjścia za pomocą tego rozwiązania? – tiktak

0

W pierwszym bloku przekroczono limit czasu dla dziecka, zabijając go i przekazując kontrolę rodzicowi. W drugim bloku limit czasu jest podniesiony w rodzicu. Dziecko nigdy nie otrzymuje sygnału.

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

+0

Wiem, że to nie jest szczegółowa odpowiedź Bill, ale tak właśnie czytałem bloki. –

+1

Blok przekazany do IO # popen jest wykonywany w kontekście procesu nadrzędnego. Nie jestem pewien, co masz na myśli mówiąc, że proces potomny może lub nie może "[uzyskać] sygnał". – pilcrow

+0

@ Jonathan Julian Miałem te dwa pliki otwarte na podzielonym ekranie, próbując to rozgryźć. O ile mogę powiedzieć, limit czasu jest tworzony z głównego wątku w obu przykładach. Oto, gdzie popen kieruje blokiem, który minął: https://github.com/ruby/ruby/blob/trunk/io.c#L6075 – llimllib