abort_on_exception が true である時に注意すべきこと

Thread.abort_on_exception や Thread#abort_on_exception が true の時、スレッドが例外を起こして終了した場合にはインタプリタ全体が停止します。ただし Thread#join した時には、例外が再びカレントスレッドに投げられます。でも、ruby1.8 と 1.9 ではその投げられる例外が違うことに注意しなければなりません。

以下のようなスクリプトを例とします。

Thread.abort_on_exception = true

thread = Thread.new do
  Thread.pass
  eval "end"
end

begin
  thread.join
rescue Object => e
  puts e.class.name
end

これは eval "end" が統語違反を犯していますので当然変数 thread に束縛されているスレッドは例外 SyntaxError を起こして停止します。また thread#join していますので、再び投げられた例外は rescue 節に補足され、その例外クラスが印字されます。

ではこれを ruby 1.8.6 で実行してみます。

% ruby test.rb
test.rb:5: (eval):1: compile error (SyntaxError)
(eval):1: syntax error, unexpected kEND	from test.rb:3:in `eval'
	from test.rb:5
	from test.rb:3:in `initialize'
	from test.rb:3:in `new'
	from test.rb:3
SystemExit

結果として SystemExit が補足されました。これは abort_on_exception が true であるために exit が実行され、それに対応する例外 SystemExit が補足されるからです。

一方 ruby 1.9 では次のようになります。

% ruby1.9 test.rb
SyntaxError

SystemExit ではなく、SyntaxError が補足されました。abort_on_exception が true であっても、exit するのではなく、例外をカレントスレッドに投げ直すだけになっています。

というわけで、ruby 1.9 の挙動の方が合理的だと思います。というのも、大抵の場合 SystemExit を受けとってもちっとも嬉しくないからです。肝心要の間違ったところの例外を受けとれなかったらrescueの意味がほとんどないと思います。また 1.8 では SystemExit を受け取る前に他のスレッドもあらかた停止しちゃってくれますので(タイミング次第なので停止しないスレッドもあったりなかったり)、これは迷惑極まりないと言えます。

つまり、やっぱり 1.9 は素晴らしい! ...というわけです。早いところ 1.9 の時代が到来することを切に祈っております。

ただしもちろん、1.8 であってもどうしもないわけではなくって、次のようにすることで例外 SyntaxError を補足できます。

Thread.abort_on_exception = true

thread = Thread.new do
  Thread.pass
  begin
    eval "end"
  rescue Exception => e
    e
  end
end

begin
  res = thread.value
  if res.kind_of?(Exception)
    raise res
  end
rescue Object => e
  puts e.class.name
end

これなら 1.8 だろうが 1.9 だろうがスレッド内で起きた例外を補足することが出来ます。とは言え、こんなゴチャゴチャとしたのは嫌ですよね。もうホント、早いところ 1.9 に移行しちゃいましょう。

ちなみに

safe-level 4 の時には Thread.abort_on_exception = true は無視されるので exit しません。safe-leve 4 環境では exit を禁止していますが、eval が禁止されていないため、そこで意図的に統語違反を起こされてしまうと全体が停止して結局 exit 出来るのと変わらないことになってしまうからです。これを覚えておくとちょっと便利かも。