From 42691ec1e0a6520163c03c3986b449caefcd98dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Lara?= Date: Sat, 4 Apr 2026 21:30:20 -0300 Subject: [PATCH] fix: deadlock using IRB integration on Rails applications When using `debugger` in a rails console using IRB integration, the second `continue` causes a fatal deadlock: "No live threads left. Deadlock?" The root cause is that `leave_subsession` pops `@subsession_stack` and sets `@tc = nil` before `thread_stopper.disable` takes full effect. In that window, the thread_stopper TracePoint fires on the main thread (back in IRB's eval loop), and since `@tc` is nil, the guard `tc == @tc` no longer protects it. The main thread is paused via `on_pause`, but the session server is already blocked on `@q_evt.pop` -- neither can wake the other. The fix for this is addiing `next unless in_subsession?` as the first guard in thread_stopper. Once `leave_subsession` has popped the stack, in_subsession? returns false and the TracePoint becomes a no-op even if it hasn't been disabled yet. --- lib/debug/session.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/debug/session.rb b/lib/debug/session.rb index 3a101d6ee..99c78af1d 100644 --- a/lib/debug/session.rb +++ b/lib/debug/session.rb @@ -1678,6 +1678,14 @@ def get_thread_client th = Thread.current private def thread_stopper TracePoint.new(:line) do + # When leave_subsession pops the subsession stack and sets @tc = nil, + # there is a window before the TracePoint is disabled where this block + # can still fire on other threads. Without this guard, the main thread + # (back in IRB's eval loop) would be paused by on_pause, but the + # session server is already waiting on @q_evt.pop -- causing a mutual + # deadlock ("No live threads left. Deadlock?"). + next unless in_subsession? + # run on each thread tc = ThreadClient.current next if tc.management?