チケット #4027 が更新されました。 (by Tomoyuki C.)
ファイル sigmask.patch 追加
元々の signal_buff の数が合わなくないのはまだ原因がわかりませんが、
シグナルマスクの処理について調べていて間違っていそうなところをみつけました。
まずささださんが指摘されていた rb_get_next_signal() で rb_enable_interrupt() でシグナルマスクを
外している点ですが、rb_get_next_signal() はタイマースレッドからのみ呼ばれるのでこれは問題なさそうです。
しかしメインスレッドその他の Thread に対応するスレッドのシグナルマスクは SIGSEGV と SIGVTALRM のみ
外されているはずなのに、全て外されていました。
このため sighandler() がタイマースレッドでなくメインスレッドで実行されています。
これは以下のような流れでおきています。
- init_sigchld で初期状態(空マスク)の sigmask が trap_last_sigmask に保存される
- タイマースレッドの起動(rb_thread_create_timer_thread)でメインスレッドはマスクがセットされる
- その後ファイルロード中に例外が生成された時に rb_trap_restore_mask() が呼ばれて trap_last_sigmask
に
保存された空マスクがセットされるためシグナルマスクが外れる
また、Signal.trap でハンドラをセットした時に、そのシグナルのシグナルマスクが外されています。
sigaction によるシグナルハンドラの実行はタイマースレッドにまかせているはずなのでこれは不要だと思います。
また例外発生時に rb_trap_restore_mask を読んでいるのも、trap() で例外が発生しても trap_ensure() で
シグナルマスクを戻す処理は行なわれているので不要なような気がします。元々どういう理由で呼ばれているのか
わからなかったので自信ないですけど。
というわけで添付のようなパッチを作成してみました。
ところがこれを当てると make test-all で Failure が 2つ増えます。
いずれも ruby のプロセスをシグナルで止めた時に終了ステータスが $?.signaled? == true でなく
$?.exited? == true になるためです。
これはどう直せばいいのかわかりません。
-
Failure:
test_should_propagate_signaled(TestBeginEndBlock)
[/Users/nagachika/opt/ruby-trunk/src/ruby-trunk/test/ruby/test_beginendblock.rb:108]:
Expected 0 to be nil. -
Failure:
test_status_kill(TestProcess)
[/Users/nagachika/opt/ruby-trunk/src/ruby-trunk/test/ruby/test_process.rb:1073]:
[s.exited?, s.signaled?, s.stopped?].
<[false, true, false]> expected but was
<[true, false, false]>.
また、sighandler() がメインスレッドから呼ばれるのは Process.spawn を実行するとまだおきてしまいます。
process.c の before_fork/after_fork で fork
の時に一時的にタイマースレッドを止めてシグナルマスクを外すためです。
こちらもどうしていいものかわからないです。
fork してそのまま動き続けるようなものはどうしようもないような気がしますが、spawn するものは
fork 後に sigprogmask で外すようにするなどでなんとかならないものでしょうか。
あーでもタイマースレッドの再起動で結局一時的に外してしまいますね……
なお、このせいで sighandler にメインスレッドとタイマースレッドで同時に実行してしまって
signal_buff のカウンタがおかしくなったのかも、とも思ったのですが、ATOMIC_INC 部分を disassemble してみると
それぞれ incl の1命令で実行されているので、仮に並列に実行されても加算もれすることはなさそうです。
プロセッサによってはダメだったりするんでしょうか。