Bug #9940
closedUnexpected throw from TracePoint hooks with recursive checking
Description
下記のようなプログラムで、2.0 と 2.1 以降で挙動が違います。
stack = []
TracePoint.new(:c_call){|tp|
mid = tp.method_id
begin
p mid
stack << mid # (*)
ensure
stack << mid
end
}.enable{
p 1
}
p stack
# Ruby 2.0 => [:p, :p, :hash, :hash, :inspect, :inspect]
# Ruby 2.1, 2.2 => [:p, :p, :hash, :inspect, :inspect]
具体的には、c-call -> :hash を実行中に、意図しない大域脱出が発生しているためです。
理由を下記に述べます。
(1) p 1
を実行するとき、rb_uninterruptible() が呼ばれます
(2) rb_uninterruptible() は、rb_hash_aset() を呼び出します
(3) rb_hash() を実行するときに、recursive check を走らせます
(4) recursive check 実行中に、rb_hash() -> #hash メソッドを呼び出します
(5) #hash メソッドが TracePoint (c_call) を呼び出します
(6) TracePoint 中で p 2
を走らせようとします
(7) (1)~(4) と同様の処理を走らせようとして、同じ key を見つけてしまい、throw して、(4) へ戻ります
(8) throw するとき、TracePoint 中の ensure 節はちゃんと実行するので、1 つだけ :hash が残ります
というわけで、TracePoint 実行するタイミングで中途半端に recursive check 用のデータが残っているのが問題です。
これを解決するために、TracePoint 実行会s時に recursive check 用のデータをクリアし、戻るときに復帰するようにしようと思います。
2.1 でもおきますが、直すにはちょっとニッチだったりしますかねぇ。
(なぜか 2.0 ではおきない)
Updated by ko1 (Koichi Sasada) over 10 years ago
- Status changed from Open to Closed
- % Done changed from 0 to 100
Applied in changeset r46419.
- vm_trace.c: clear and restore recursive checking thread local data
to avoid unexpected throw from TracePoint.
[Bug #9940] - test/ruby/test_settracefunc.rb: add a test.
- thread.c: adde
- rb_threadptr_reset_recursive_data(rb_thread_t *th);
- rb_threadptr_restore_recursive_data(rb_thread_t *th, VALUE old);
- vm_core.h: ditto.
Updated by nagachika (Tomoyuki Chikanaga) over 10 years ago
- Backport changed from 2.0.0: UNKNOWN, 2.1: REQUIRED to 2.0.0: UNKNOWN, 2.1: DONE
r46419 and r46429 were backported into ruby_2_1
branch at r47009.
Updated by usa (Usaku NAKAMURA) about 10 years ago
- Backport changed from 2.0.0: UNKNOWN, 2.1: DONE to 2.0.0: DONTNEED, 2.1: DONE