- blogs:
- cles::blog

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

Rubyでスレッドをつかったプログラムを作ったら、凡ミスを修正するのに苦戦してしまったのでメモ。結論から述べると、ThreadはjoinしたときにThread内で起こった例外が再度スローされるので、自分で立てたスレッドを確実にjoinすることがエラーハンドリングでは重要みたい。
例えば下記のコードはhogeという関数は定義されていないので、スレッドt1はこの部分でエラーになるはず。ところが、このプログラムを起動してみると、意図した動作もせず、エラーも出ないという不思議な状態になります。今回はこの状態だったので、手がかりが少なくていろいろと大変でした。
test0.rb
test0.rb実行結果
† エラーの起こるスレッドをjoinする
このようにエラーが起こるスレッドをjoinすれば例外を認識できるようになります。
diff -u test0.rb test1.rb
test1.rb実行結果
† 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
test2.rb実行結果
† つかったThreadを両方ともjoinする
この場合はThread.abort_on_exceptionの値に関わらず、join時に例外が補足できています。
diff -u test0.rb test3.rb
test3.rb実行結果
† エラーの起こっていないスレッドをjoinする
ある意味一番不可解なのがこのパターンかもしれません。deadlockとか言われるし。
例のようにenqするスレッドとdeqするスレッドがQueueでつながってる場合に、enqする側が死んでしまうと、deqする側が永久の待ちにはいってしまうので、deq側をjoinしようとするとプログラムの根本的な原因ではなく、ランタイムで発生したdeadlockが表示されてしまいます。
diff -u test0.rb test4.rb
test4.rb実行結果
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/3426
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。
OpenID を使ってログインすることができます。
2 . 福岡銀がデマの投稿者への刑事告訴を検討中(110748)
3 . 年次の人間ドックへ(110354)
4 . 2023 年分の確定申告完了!(1つめ)(109905)
5 . 三菱鉛筆がラミーを買収(109804)