Hatena::Groupkwfsws

kiwofusiの作業ログ このページをアンテナに追加 RSSフィード

 | 

2011-12-31

Rubyの例外処理で Exception と StandardError の違いを意識していなくてはまった&timeoutを入れ子にしたときの例外処理について実験

15:52 | はてなブックマーク - Rubyの例外処理で Exception と StandardError の違いを意識していなくてはまった&timeoutを入れ子にしたときの例外処理について実験 - kiwofusiの作業ログ

Rubyメモ。

Exception と StandardError の違い

何も考えずに例外処理するときに

begin
	# hoge
rescue Exception => e
	$stderr.puts e
end

などと書いていたのですが、さいきんは

begin
	# hoge
rescue => e
	$stderr.puts e
end

と省略していました。でも、実は「省略」じゃなくてこれらは別物なんですね。「rescue => e」は

begin
	# hoge
rescue StandardError => e
	$stderr.puts e
end

と同じ意味です。こっちこそがふつうの例外処理です。

「ふつうの例外処理」のつもりで「rescue Exception => e」としてしまうと「Timeout::Error」などでプログラムを停止させたいに困るかもしれません。

タイムアウトしねーぞ! なんだこりゃ! とはまっていました。

timeoutを入れ子にしたときの例外処理

プログラム全体にタイムリミットをつけて、そのなかでネットアクセスをする、などとするとtimeoutブロックあるいはTimeout::Errorの発生箇所が入れ子になります。こういう場合の例外処理について実験してみました。結論として、ちゃんと例外クラスを書いておけばうまく入れ子のレベルを判断してくれるみたいです。

実験環境

ruby 1.8.7 (2011-06-30 patchlevel 352)

子がタイムアウトする場合
require 'timeout'
begin # 子がタイムアウトする場合
  timeout(6) do
    begin # 親:残り6秒
      timeout(3) do
        sleep 4 # 子:タイムアウト
      end
    rescue Timeout::Error => e
      puts "child timeout: #{e.class}"
    end # 親:残り時間3秒
    sleep 4 # 親:タイムアウト
  end
rescue Timeout::Error => e
  puts "parent timeout: #{e.class}"
end
#=> child timeout: Timeout::Error
#   parent timeout: Timeout::Error

子がタイムアウト→子の例外処理→親がタイムアウト→親の例外処理

ふつうですね。

子の処理中に親がタイムアウトする場合 rescue Timeout::Error
require 'timeout'
begin # 子の処理中に親がタイムアウトする場合
  timeout(6) do
    begin # 親:6秒
      timeout(7) do
        sleep 8
        # 6秒後→親:タイムアウト
        # 7秒後→子:タイムアウト
      end
    rescue Timeout::Error => e
      puts "child timeout: #{e.class}"
    end
  end
rescue Timeout::Error => e
  puts "parent timeout: #{e.class}"
end
#=> parent timeout: Timeout::Error

例外の発生箇所は子の中ですが、子の例外処理をスルーしています。

原理はわかりませんが、うれしい仕様です。

子の処理中に親がタイムアウトする場合 rescue Exception

子の rescue を Timeout::Error ではなく Exception としてみます。

require 'timeout'
begin # 子の処理中に親がタイムアウトする場合
  timeout(6) do
    begin # 親:6秒
      timeout(7) do
        sleep 8
        # 6秒後→親:タイムアウト
        # 7秒後→子:タイムアウト
      end
    rescue Exception => e
      puts "child timeout: #{e.class}"
    end
  end
rescue Timeout::Error => e
  puts "parent timeout: #{e.class}"
end
#=> child timeout: #<Class:0x801012fa8>

よくわからない例外クラスを子がキャッチしています。要注意ですね。

タイムアウトを管理したいときはちゃんと rescue に Timeout::Error を指定しましょう、ということでした。

参考

逆引きRuby - 例外

Rubyの組み込み例外クラスを調べる | Kwappa研究開発室

Timeout::Errorに注意 - dreammindの日記

class Timeout::Error:「timeout を使うライブラリを作成する場合は、ユーザが指定した timeout を捕捉しないようにライブラリ内で TimeoutError のサブクラスを定義して使用した方が無難です。 」

lang.ruby.japanese - [ruby-list:44685] Re: beginで捉えられないエラー?? - msg#00132 - OSDir.com:「最近のruby-coreでの議論の結果、timeoutがネストしている場合でもそれぞれを区別するようになり」

o_showo_show2012/01/09 21:49Ruby1.9系では、「rescue => e」の省略形でも
Timeout::Errorを補足することができますね
(つまり、Timeout::ErrorがStandardErrorから継承されている)。

Timeout::Error.ancestorsを1.8系と1.9系で確認すると、
明らかに継承の階層が違うことがわかります。

…と思ったんですけど、Rubyリファレンスマニュアルだと
1.9.3でも継承の階層が1.8系と同じになってますね。あとで報告しておきます。
http://doc.okkez.net/static/193/class/Timeout=3a=3aError.html

o_showo_show2012/01/09 21:52>(つまり、Timeout::ErrorがStandardErrorから継承されている)
これだとStandardErrorが直接の親みたいにも読めるのでちょっと訂正。
「1.9系では、Timeout::Errorの継承の祖先には、StandardErrorがいる」。

 |