BLOGTIMES
2010/02/16

Rubyのスレッド中の例外でハマった

  ruby 
このエントリーをはてなブックマークに追加

Rubyでスレッドをつかったプログラムを作ったら、凡ミスを修正するのに苦戦してしまったのでメモ。結論から述べると、ThreadはjoinしたときにThread内で起こった例外が再度スローされるので、自分で立てたスレッドを確実にjoinすることがエラーハンドリングでは重要みたい。

例えば下記のコードはhogeという関数は定義されていないので、スレッドt1はこの部分でエラーになるはず。ところが、このプログラムを起動してみると、意図した動作もせず、エラーも出ないという不思議な状態になります。今回はこの状態だったので、手がかりが少なくていろいろと大変でした。

test0.rb

require 'thread' Thread.abort_on_exception = false ### q = Queue.new t1 = Thread.new do hoge ### 10.times do |i| q.enq "message! #{i}" end q.enq nil end t2 = Thread.new do while i = q.deq puts i end end #t1.join ### #t2.join ###

test0.rb実行結果

ruby test0.rb

エラーの起こるスレッドをjoinする

このようにエラーが起こるスレッドをjoinすれば例外を認識できるようになります。

diff -u test0.rb test1.rb

--- test0.rb 2010-02-16 23:43:43.000000000 +0900 +++ test1.rb 2010-02-16 23:44:27.000000000 +0900 @@ -17,5 +17,5 @@ end end -#t1.join ### +t1.join ### #t2.join ###

test1.rb実行結果

ruby test1.rb test1.rb:7: undefined local variable or method `hoge' for main:Object (NameError) from test1.rb:20:in `join' from test1.rb:20

Thread.abort_on_exceptionをtrueにする

Threadクラスには例外によってスレッドが中断したときの扱いを決定するためのabort_on_exceptionという属性があり、これをtrueに設定しておくとthreadでエラーが例外が発生したときに即時にインタプリタが中断されるようになります。

Thread - Rubyリファレンスマニュアル

Thread.abort_on_exception = newstate
真の時は、いずれかのスレッドが例外によって終了した時に、インタプリタ全体を中断させます。デフォルトは偽、すなわち、通常あるスレッドで起こった例外は、Thread#join などで検出されない限りそのスレッドだけをなにも警告を出さずに終了させます。スレッドと例外に詳述。

開発、デバッグ時にはこれをtrueにしておけば、joinし忘れていても例外の認識が容易になります。ただし、Threadをきちんとjoinしていなくて、実運用時にこの値をfalseに戻して運用したりすると、エラーが出ないので(このエントリのtest0.rbのような状態になって)原因究明に手間取る可能性があるので注意した方がよさそうです。

diff -u test0.rb test2.rb

--- test0.rb 2010-02-16 23:43:43.000000000 +0900 +++ test2.rb 2010-02-16 23:41:26.000000000 +0900 @@ -1,6 +1,6 @@ require 'thread' -Thread.abort_on_exception = false ### +Thread.abort_on_exception = true ### q = Queue.new t1 = Thread.new do

test2.rb実行結果

ruby test2.rb test2.rb:7: undefined local variable or method `hoge' for main:Object (NameError) from test2.rb:6:in `initialize' from test2.rb:6:in `new' from test2.rb:6

つかったThreadを両方ともjoinする

この場合はThread.abort_on_exceptionの値に関わらず、join時に例外が補足できています。

diff -u test0.rb test3.rb

--- test0.rb 2010-02-16 23:43:43.000000000 +0900 +++ test3.rb 2010-02-16 23:41:50.000000000 +0900 @@ -17,5 +17,5 @@ end end -#t1.join ### -#t2.join ### +t1.join ### +t2.join ###

test3.rb実行結果

ruby test3.rb test3.rb:7: undefined local variable or method `hoge' for main:Object (NameError) from test3.rb:20:in `join' from test3.rb:20

エラーの起こっていないスレッドをjoinする

ある意味一番不可解なのがこのパターンかもしれません。deadlockとか言われるし。

例のようにenqするスレッドとdeqするスレッドがQueueでつながってる場合に、enqする側が死んでしまうと、deqする側が永久の待ちにはいってしまうので、deq側をjoinしようとするとプログラムの根本的な原因ではなく、ランタイムで発生したdeadlockが表示されてしまいます。

diff -u test0.rb test4.rb

--- test0.rb 2010-02-16 23:43:43.000000000 +0900 +++ test4.rb 2010-02-16 23:46:08:24.000000000 +0900 @@ -18,4 +18,4 @@ end #t1.join ### -#t2.join ### +t2.join ###

test4.rb実行結果

ruby test4.rb deadlock 0xb7f23138: sleep:- - /usr/lib/ruby/1.8/thread.rb:318 deadlock 0xb7f377c8: sleep:J(0xb7f23138) (main) - test4.rb:21 test4.rb:21:in `join': Thread(0xb7f377c8): deadlock (fatal) from test4.rb:21

    トラックバックについて
    Trackback URL:
    お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
    このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3426
    Trackbacks
    このエントリにトラックバックはありません
    Comments
    愛のあるツッコミをお気軽にどうぞ。[policy]
    古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
    コメントはありません
    Comments Form

    コメントは承認後の表示となります。
    OpenIDでログインすると、即時に公開されます。

    OpenID を使ってログインすることができます。

    Identity URL: Yahoo! JAPAN IDでログイン