https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/135956
>From 215f32820d09d215f2c1b4f4c4c0e0fbe9437264 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere <jo...@devlieghere.com> Date: Wed, 16 Apr 2025 15:55:25 +0200 Subject: [PATCH] [lldb] Fix lock inversion between statusline mutex and output mutex Fix a deadlock between the statusline mutex (in Debugger) and the output file mutex (in LockedStreamFile). The deadlock occurs when the main thread is calling the statusline callback while holding the output mutex in Editline, while the default event thread is trying to update the statusline. Extend the uncritical section so we can redraw the statusline there. The loop in Editline::GetCharacter should be unnecessary. It would only loop if we had a successful read with length zero, which shouldn't be possible or when we can't convert a partial UTF-8 character, in which case we bail out. --- lldb/source/Host/common/Editline.cpp | 93 +++++++++---------- .../statusline/TestStatusline.py | 17 ++++ 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp index 29abaf7c65f28..ff71cd0cdb241 100644 --- a/lldb/source/Host/common/Editline.cpp +++ b/lldb/source/Host/common/Editline.cpp @@ -567,9 +567,6 @@ int Editline::GetCharacter(EditLineGetCharType *c) { m_needs_prompt_repaint = false; } - if (m_redraw_callback) - m_redraw_callback(); - if (m_multiline_enabled) { // Detect when the number of rows used for this input line changes due to // an edit @@ -585,54 +582,56 @@ int Editline::GetCharacter(EditLineGetCharType *c) { m_current_line_rows = new_line_rows; } + if (m_terminal_size_has_changed) + ApplyTerminalSizeChange(); + + // This mutex is locked by our caller (GetLine). Unlock it while we read a + // character (blocking operation), so we do not hold the mutex + // indefinitely. This gives a chance for someone to interrupt us. After + // Read returns, immediately lock the mutex again and check if we were + // interrupted. + m_locked_output.reset(); + + if (m_redraw_callback) + m_redraw_callback(); + // Read an actual character - while (true) { - lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; - char ch = 0; - - if (m_terminal_size_has_changed) - ApplyTerminalSizeChange(); - - // This mutex is locked by our caller (GetLine). Unlock it while we read a - // character (blocking operation), so we do not hold the mutex - // indefinitely. This gives a chance for someone to interrupt us. After - // Read returns, immediately lock the mutex again and check if we were - // interrupted. - m_locked_output.reset(); - int read_count = - m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr); - m_locked_output.emplace(m_output_stream_sp->Lock()); - if (m_editor_status == EditorStatus::Interrupted) { - while (read_count > 0 && status == lldb::eConnectionStatusSuccess) - read_count = - m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr); - lldbassert(status == lldb::eConnectionStatusInterrupted); - return 0; - } + lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess; + char ch = 0; + int read_count = + m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr); + + // Re-lock the output mutex to protected m_editor_status here and in the + // switch below. + m_locked_output.emplace(m_output_stream_sp->Lock()); + if (m_editor_status == EditorStatus::Interrupted) { + while (read_count > 0 && status == lldb::eConnectionStatusSuccess) + read_count = + m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr); + lldbassert(status == lldb::eConnectionStatusInterrupted); + return 0; + } - if (read_count) { - if (CompleteCharacter(ch, *c)) - return 1; - } else { - switch (status) { - case lldb::eConnectionStatusSuccess: // Success - break; + if (read_count) { + if (CompleteCharacter(ch, *c)) + return 1; + return 0; + } - case lldb::eConnectionStatusInterrupted: - llvm_unreachable("Interrupts should have been handled above."); - - case lldb::eConnectionStatusError: // Check GetError() for details - case lldb::eConnectionStatusTimedOut: // Request timed out - case lldb::eConnectionStatusEndOfFile: // End-of-file encountered - case lldb::eConnectionStatusNoConnection: // No connection - case lldb::eConnectionStatusLostConnection: // Lost connection while - // connected to a valid - // connection - m_editor_status = EditorStatus::EndOfInput; - return 0; - } - } + switch (status) { + case lldb::eConnectionStatusSuccess: + llvm_unreachable("Success should have resulted in positive read_count."); + case lldb::eConnectionStatusInterrupted: + llvm_unreachable("Interrupts should have been handled above."); + case lldb::eConnectionStatusError: + case lldb::eConnectionStatusTimedOut: + case lldb::eConnectionStatusEndOfFile: + case lldb::eConnectionStatusNoConnection: + case lldb::eConnectionStatusLostConnection: + m_editor_status = EditorStatus::EndOfInput; } + + return 0; } const char *Editline::Prompt() { diff --git a/lldb/test/API/functionalities/statusline/TestStatusline.py b/lldb/test/API/functionalities/statusline/TestStatusline.py index 747a7a14e0629..489c0371b11d8 100644 --- a/lldb/test/API/functionalities/statusline/TestStatusline.py +++ b/lldb/test/API/functionalities/statusline/TestStatusline.py @@ -77,3 +77,20 @@ def test_no_color(self): "\x1b[7m", ], ) + + def test_deadlock(self): + """Regression test for lock inversion between the statusline mutex and + the output mutex.""" + self.build() + self.launch(extra_args=["-o", "settings set use-color false"]) + self.child.expect("(lldb)") + + # Change the terminal dimensions. + terminal_height = 10 + terminal_width = 60 + self.child.setwinsize(terminal_height, terminal_width) + + exe = self.getBuildArtifact("a.out") + + self.expect("file {}".format(exe), ["Current executable"]) + self.expect("help", ["Debugger commands"]) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits