https://github.com/jimingham created 
https://github.com/llvm/llvm-project/pull/179789

We have gotten reports of an occasional deadlock on shutdown.  The 
Driver::MainLoop thread is shutting down, and gets stuck here:

  Thread 0x3c017c
  start 
    main 
       Driver::MainLoop() 
         lldb::SBDebugger::RunCommandInterpreter(bool, bool) 
           
lldb_private::CommandInterpreter::RunCommandInterpreter(lldb_private::CommandInterpreterRunOptions&)
            lldb_private::Debugger::RunIOHandlers()
              
lldb_private::Debugger::PopIOHandler(std::__1::shared_ptr<lldb_private::IOHandler>
 const&)
                 lldb_private::Editline::Cancel()
                   lldb_private::LockableStreamFile::Lock()
                     std::__1::recursive_mutex::lock()

It has acquired the IO Handler list lock (in PopIOHandler) and is stuck trying 
to get the debugger output stream lock.

Meanwhile, the event-handler thread is doing:

  Thread 0x3c01d3
  thread_start
    _pthread_start
       lldb_private::HostThreadMacOSX::ThreadCreateTrampoline(void*) 
        lldb_private::HostNativeThreadBase::ThreadCreateTrampoline(void*)
          
std::__1::__function::__func<lldb_private::Debugger::StartEventHandlerThread()::$_0,
 void* ()>::operator()() 
            lldb_private::Debugger::DefaultEventHandler()
              lldb_private::Statusline::~Statusline()
                
lldb_private::Statusline::UpdateScrollWindow(lldb_private::Statusline::ScrollWindowMode)
                  lldb_private::Debugger::RefreshIOHandler() 
                    std::__1::recursive_mutex::lock()

UpdateScrollWindow gets the debugger output stream lock, and sends some data to 
the output, then it calls RefreshIOHandler while still holding the Debugger 
output stream lock.  That's the problem, since if it gets that lock before 
Editline::Cancel() completes, we'll get this deadlock.

The solution is simple, there's no reason why UpdateScrollWindow should hold 
the debugger output lock when it calls RefreshIOHandler.  If that refresh ends 
up needing to write to the debugger output, it can take the lock more narrowly 
at that point.  This fixed the deadlock because after yielding the output lock, 
the second thread waits on the first to get the IO Handler lock.  Meanwhile the 
first thread can now acquire the debugger output lock and finish the Cancel, 
and exit PopIOHandler which will allow the second thread to make progress in 
turn.

I couldn't figure out any way to test this...

>From 3b655a426418d0c109dab4bc4517796acaf19f23 Mon Sep 17 00:00:00 2001
From: Jim Ingham <[email protected]>
Date: Wed, 4 Feb 2026 13:36:27 -0800
Subject: [PATCH] Prevents a potential lock inversion in StatusLine when
 shutting down.

---
 lldb/source/Core/Statusline.cpp | 57 +++++++++++++++++----------------
 1 file changed, 29 insertions(+), 28 deletions(-)

diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
index 47d48b8474e37..bdc649580637c 100644
--- a/lldb/source/Core/Statusline.cpp
+++ b/lldb/source/Core/Statusline.cpp
@@ -92,35 +92,36 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) {
     return;
 
   const unsigned reduced_scroll_rows = m_terminal_height - 1;
-  LockedStreamFile locked_stream = stream_sp->Lock();
-
-  switch (mode) {
-  case EnableStatusline:
-    // Move everything on the screen up.
-    locked_stream << '\n';
-    locked_stream.Printf(ANSI_UP_ROWS, 1);
-    // Reduce the scroll window.
-    locked_stream << ANSI_SAVE_CURSOR;
-    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_rows);
-    locked_stream << ANSI_RESTORE_CURSOR;
-    break;
-  case DisableStatusline:
-    // Reset the scroll window.
-    locked_stream << ANSI_SAVE_CURSOR;
-    locked_stream.Printf(ANSI_SET_SCROLL_ROWS,
-                         static_cast<unsigned>(m_terminal_height));
-    locked_stream << ANSI_RESTORE_CURSOR;
-    // Clear the screen below to hide the old statusline.
-    locked_stream << ANSI_CLEAR_BELOW;
-    break;
-  case ResizeStatusline:
-    // Clear the screen and update the scroll window.
-    // FIXME: Find a better solution (#146919).
-    locked_stream << ANSI_CLEAR_SCREEN;
-    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_rows);
-    break;
+  { // Scope for locked_stream:
+    LockedStreamFile locked_stream = stream_sp->Lock();
+
+    switch (mode) {
+    case EnableStatusline:
+      // Move everything on the screen up.
+      locked_stream << '\n';
+      locked_stream.Printf(ANSI_UP_ROWS, 1);
+      // Reduce the scroll window.
+      locked_stream << ANSI_SAVE_CURSOR;
+      locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_rows);
+      locked_stream << ANSI_RESTORE_CURSOR;
+      break;
+    case DisableStatusline:
+      // Reset the scroll window.
+      locked_stream << ANSI_SAVE_CURSOR;
+      locked_stream.Printf(ANSI_SET_SCROLL_ROWS,
+                           static_cast<unsigned>(m_terminal_height));
+      locked_stream << ANSI_RESTORE_CURSOR;
+      // Clear the screen below to hide the old statusline.
+      locked_stream << ANSI_CLEAR_BELOW;
+      break;
+    case ResizeStatusline:
+      // Clear the screen and update the scroll window.
+      // FIXME: Find a better solution (#146919).
+      locked_stream << ANSI_CLEAR_SCREEN;
+      locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_rows);
+      break;
+    }
   }
-
   m_debugger.RefreshIOHandler();
 }
 

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to