From: "NAKAMURA, Hiroshi" Date: 2000-12-26T19:37:06+09:00 Subject: [ruby-dev:11867] [Patch] debug.rb: Re: Debugging thread なひです。再挑戦。 > From: NAKAMURA, Hiroshi [mailto:nakahiro@sarion.co.jp] > Sent: Tuesday, December 26, 2000 12:26 AM > > そのように変更しました。^Cやbpでは、全スレッドが止まります。 > > n/s/cでは全スレッドを再開します。 > > すいません、まだどこか甘いみたいです。 > スレッドを大量に作るアプリで、deadlockする場合があります。 > 調査します。 スレッドの量は関係ありませんでした。 1. とあるスレッドが^Cやbpや例外で止まる 2. カレントスレッド以外の全スレッドに対し 「次に実行する時に止まってねフラグ」をたてる 3. 各スレッドが次々に止まり(Thread#stop)、フラグはクリアされる 4. n/s/cで実行を再開しようとする 5. カレントスレッド以外の全スレッドを走らせる(Thread.run) 6. カレントスレッドの処理を継続する という流れなんですが、3の前に5が起こると、 「次に実行する時に止まってねフラグ」が立ったまま スレッドが走り出すことになり、結果、走らせたスレッドはすぐに止まります。 この時、2の全スレッドが既にThread#stopしており、 また6で処理を継続したカレントスレッドが すぐにThread#stopするコードだったりすると、 全スレッドがstopしてスケジューラがハングします。 具体例: WEBrickのサンプルHTTPサーバーでは、 ディスパッチャスレッドがqueue#pushしたものを、 全ワーカスレッドがqueue#popしています。 ブラウザがアクセスすると、このqueue経由で ワーカスレッドにsocketが渡されますが、 そのsocketでEPIPEが発生すると上記1.状態になります。 その他の全スレッドはqueue#popすなわちThread#stopしており、 2でフラグは立つものの、クリアされません。 カレントスレッドは再開後、次の接続を待ってすぐにqueue#popすなわち Thread#stopします。。。 誰か読んでますかー。 というわけでpatchです。5の実行の前に、全フラグをクリアします。 さらに、finishコマンドで止まると、再開後に一度余計に止まる、 というバグを直しました。 なひもChangeLogっぽく書いてみたりして。 * lib/debug.rb: Avoid thread deadlock in debugging stopped thread. * lib/debug.rb: Uncleared 'finish' state. --- debug.rb.ruby-dev:11846 Mon Dec 25 16:08:20 2000 +++ debug.rb Tue Dec 26 18:46:41 2000 @@ -99,11 +99,23 @@ end - def suspend + def set_suspend @suspend_next = true end + def clear_suspend + @suspend_next = false + end + + def suspend_all + DEBUGGER__.suspend + end + + def resume_all + DEBUGGER__.resume + end + def check_suspend while (Thread.critical = true; @suspend_next) - waiting.push Thread.current + DEBUGGER__.waiting.push Thread.current @suspend_next = false Thread.stop @@ -132,6 +144,6 @@ end - def waiting - DEBUGGER__.waiting + def context(th) + DEBUGGER__.context(th) end @@ -140,4 +152,8 @@ end + def set_last_thread(th) + DEBUGGER__.set_last_thread(th) + end + def debug_eval(str, binding) begin @@ -238,5 +254,5 @@ def debug_command(file, line, id, binding) MUTEX.lock - DEBUGGER__.set_last_thread(Thread.current) + set_last_thread(Thread.current) frame_pos = 0 binding_file = file @@ -507,5 +523,5 @@ end MUTEX.unlock - DEBUGGER__.resume_all_thread + resume_all end @@ -663,5 +679,5 @@ end end - DEBUGGER__.suspend_all_thread + suspend_all debug_command(file, line, id, binding) end @@ -670,5 +686,5 @@ def trace_func(event, file, line, id, binding, klass) Tracer.trace_func(event, file, line, id, binding, klass) if trace? - DEBUGGER__.context(Thread.current).check_suspend + context(Thread.current).check_suspend @file = file @line = line @@ -688,5 +704,5 @@ else @no_step = nil - DEBUGGER__.suspend_all_thread + suspend_all debug_command(file, line, id, binding) @last = [file, line] @@ -698,5 +714,5 @@ if check_break_points(file, id.id2name, binding, id) or check_break_points(klass.to_s, id.id2name, binding, id) - DEBUGGER__.suspend_all_thread + suspend_all debug_command(file, line, id, binding) end @@ -711,4 +727,5 @@ if @frames.size == @finish_pos @stop_next = 1 + @finish_pos = 0 end @frames.shift @@ -768,10 +785,10 @@ end - def suspend_all_thread + def suspend Thread.critical = true make_thread_list for th in @thread_list next if th[0] == Thread.current - context(th[0]).suspend + context(th[0]).set_suspend end Thread.critical = false @@ -780,6 +797,11 @@ end - def resume_all_thread + def resume Thread.critical = true + make_thread_list + for th in @thread_list + next if th[0] == Thread.current + context(th[0]).clear_suspend + end waiting.each do |th| th.run