Files
llvm-project/lldb/source/Core/Statusline.cpp
jimingham fbb371affa Prevents a potential lock inversion in StatusLine when shutting down. (#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...
2026-02-05 10:28:51 -08:00

5.2 KiB