Ruby 2.3.0 リファレンスマニュアル > ライブラリ一覧 > timeoutライブラリ > Timeoutモジュール
クラスの継承リスト: Timeout
タイムアウトを行うためのモジュールです。
timeout(sec, exception_class = nil) {|i| ... } -> object
[permalink][rdoc]ブロックを 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")