タイムアウトを行うためのモジュールです。
timeout(sec, exception_class = nil) {|i| ... } -> object
[permalink][rdoc]timeout(sec, exception_class, message) {|i| ... } -> object
ブロックを sec 秒の期限付きで実行します。ブロックの実行時間が制限を過ぎたときは例外 Timeout::Error が発生します。
exception_class を指定した場合には Timeout::Error の代わりにその例外が発生します。ブロックパラメータ i は sec がはいります。
また sec が 0 もしくは nil のときは制限時間なしでブロックを実行します。
例 長い計算のタイムアウト
require 'timeout' def calc_pi(min) loop do x = rand y = rand x**2 + y**2 < 1.0 ? min[0] += 1 : min[1] += 1 end end t = 5 min = [ 0, 0] begin Timeout.timeout(t){ calc_pi(min) } rescue Timeout::Error puts "timeout" end printf "%d: pi = %f\n", min[0] + min[1], min[0]*4.0/(min[0]+min[1]) #例 #=> 417519: pi = 3.141443
例 独自の例外を発生させるタイムアウト
#!/usr/bin/env ruby require 'timeout' class MYError < Exception;end begin Timeout.timeout(5, MYError) { sleep(30) } rescue MYError => err puts "MYError" puts err end
timeout による割り込みは Thread によって実現されています。 C 言語レベルで実装され、 Ruby のスレッドが割り込めない処理に対して timeout は無力です。そのようなものは実用レベルでは少ないのですが、 Socket などは DNSの名前解決に時間がかかった場合割り込めません (resolv-replace を使用する必要があります)。その処理を Ruby で実装しなおすか C 側で Ruby のスレッドを意識してあげる必要があります。
以下の例では、gethostbyname(およそ0.6秒処理に時間がかかっている) が終了した直後((A)の箇所)で TimeoutError 例外があがっています。
例 timeout が割り込めない
require 'timeout' require 'socket' t = 0.1 start = Time.now begin Timeout.timeout(t) { p TCPSocket.gethostbyname("www.ruby-lang.org") # (A) } ensure p Time.now - start end # 実行例 => ["helium.ruby-lang.org", [], 2, "210.251.121.214"] 0.689331 /usr/local/lib/ruby/1.6/timeout.rb:37: execution expired (TimeoutError) from -:6:in `timeout' from -:6 # gethostbyname が0.1秒かからない場合は例外が発生しないので # その場合は、t に小さい数値(0.000001のような)に変える。
timeout による割り込みは Kernel.#system によって呼び出された外部プログラムをタイムアウトさせる事はできないので、IO.popen、Kernel.#openを使用するなどの工夫が必要です。
例 外部コマンドのタイムアウト
require 'timeout' # テスト用のシェルをつくる。 File.open("loop.sh", "w"){|fp| fp.print <<SHELL_EOT #!/bin/bash S="scale=10" M=32767 trap 'echo "$S; $m1/($m1+$m2)*4" | bc ; echo "count = $((m1+m2))" ; exit 0' INT m1=0 m2=0 while true do x="($RANDOM/$M)" y="($RANDOM/$M)" c=$(echo "$S;$x^2+$y^2 < 1.0" | bc) echo $x $y $c if [ $c -eq 1 ] then let m1++ else let m2++ fi done SHELL_EOT } File.chmod(0755, "loop.sh") t = 10 # 10 秒でタイムアウト begin pid = nil com = nil Timeout.timeout(t) { # system だととまらない # system("./loop.sh") com = IO.popen("./loop.sh") pid = com.pid while line = com.gets print line end } rescue Timeout::Error => err puts "timeout: shell execution." Process.kill('SIGINT', pid) printf "[result]\t%s", com.read com.close unless com.nil? end #止まっているか確認する。 #system("ps au")