Unexpected throw from TracePoint hooks with recursive checking

Issue #9940 has been updated by Usaku NAKAMURA.

Backport changed from 2.0.0: UNKNOWN, 2.1: DONE to 2.0.0: DONTNEED, 2.1:
DONE


Bug #9940: Unexpected throw from TracePoint hooks with recursive
checking
https://bugs.ruby-lang.org/issues/9940#change-48588

  • Author: Koichi Sasada
  • Status: Closed
  • Priority: Normal
  • Assignee: Koichi Sasada
  • Category: core
  • Target version: current: 2.2.0
  • ruby -v: ruby 2.2.0dev (2014-06-13 trunk 46416) [i386-mswin32_110]
  • Backport: 2.0.0: DONTNEED, 2.1: DONE

下記のようなプログラムで、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 ではおきない)