Skip to content

Commit 9d7ff58

Browse files
committed
[lldb] Fix lock inversion between statusline mutex and output mutex (llvm#135956)
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. rdar://149251156 (cherry picked from commit 8311620)
1 parent 956f6b5 commit 9d7ff58

File tree

2 files changed

+63
-47
lines changed

2 files changed

+63
-47
lines changed

lldb/source/Host/common/Editline.cpp

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,6 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
567567
m_needs_prompt_repaint = false;
568568
}
569569

570-
if (m_redraw_callback)
571-
m_redraw_callback();
572-
573570
if (m_multiline_enabled) {
574571
// Detect when the number of rows used for this input line changes due to
575572
// an edit
@@ -585,54 +582,56 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
585582
m_current_line_rows = new_line_rows;
586583
}
587584

585+
if (m_terminal_size_has_changed)
586+
ApplyTerminalSizeChange();
587+
588+
// This mutex is locked by our caller (GetLine). Unlock it while we read a
589+
// character (blocking operation), so we do not hold the mutex
590+
// indefinitely. This gives a chance for someone to interrupt us. After
591+
// Read returns, immediately lock the mutex again and check if we were
592+
// interrupted.
593+
m_locked_output.reset();
594+
595+
if (m_redraw_callback)
596+
m_redraw_callback();
597+
588598
// Read an actual character
589-
while (true) {
590-
lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
591-
char ch = 0;
592-
593-
if (m_terminal_size_has_changed)
594-
ApplyTerminalSizeChange();
595-
596-
// This mutex is locked by our caller (GetLine). Unlock it while we read a
597-
// character (blocking operation), so we do not hold the mutex
598-
// indefinitely. This gives a chance for someone to interrupt us. After
599-
// Read returns, immediately lock the mutex again and check if we were
600-
// interrupted.
601-
m_locked_output.reset();
602-
int read_count =
603-
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
604-
m_locked_output.emplace(m_output_stream_sp->Lock());
605-
if (m_editor_status == EditorStatus::Interrupted) {
606-
while (read_count > 0 && status == lldb::eConnectionStatusSuccess)
607-
read_count =
608-
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
609-
lldbassert(status == lldb::eConnectionStatusInterrupted);
610-
return 0;
611-
}
599+
lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
600+
char ch = 0;
601+
int read_count =
602+
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
603+
604+
// Re-lock the output mutex to protected m_editor_status here and in the
605+
// switch below.
606+
m_locked_output.emplace(m_output_stream_sp->Lock());
607+
if (m_editor_status == EditorStatus::Interrupted) {
608+
while (read_count > 0 && status == lldb::eConnectionStatusSuccess)
609+
read_count =
610+
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
611+
lldbassert(status == lldb::eConnectionStatusInterrupted);
612+
return 0;
613+
}
612614

613-
if (read_count) {
614-
if (CompleteCharacter(ch, *c))
615-
return 1;
616-
} else {
617-
switch (status) {
618-
case lldb::eConnectionStatusSuccess: // Success
619-
break;
615+
if (read_count) {
616+
if (CompleteCharacter(ch, *c))
617+
return 1;
618+
return 0;
619+
}
620620

621-
case lldb::eConnectionStatusInterrupted:
622-
llvm_unreachable("Interrupts should have been handled above.");
623-
624-
case lldb::eConnectionStatusError: // Check GetError() for details
625-
case lldb::eConnectionStatusTimedOut: // Request timed out
626-
case lldb::eConnectionStatusEndOfFile: // End-of-file encountered
627-
case lldb::eConnectionStatusNoConnection: // No connection
628-
case lldb::eConnectionStatusLostConnection: // Lost connection while
629-
// connected to a valid
630-
// connection
631-
m_editor_status = EditorStatus::EndOfInput;
632-
return 0;
633-
}
634-
}
621+
switch (status) {
622+
case lldb::eConnectionStatusSuccess:
623+
llvm_unreachable("Success should have resulted in positive read_count.");
624+
case lldb::eConnectionStatusInterrupted:
625+
llvm_unreachable("Interrupts should have been handled above.");
626+
case lldb::eConnectionStatusError:
627+
case lldb::eConnectionStatusTimedOut:
628+
case lldb::eConnectionStatusEndOfFile:
629+
case lldb::eConnectionStatusNoConnection:
630+
case lldb::eConnectionStatusLostConnection:
631+
m_editor_status = EditorStatus::EndOfInput;
635632
}
633+
634+
return 0;
636635
}
637636

638637
const char *Editline::Prompt() {

lldb/test/API/functionalities/statusline/TestStatusline.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,20 @@ def test_no_color(self):
7777
"\x1b[7m",
7878
],
7979
)
80+
81+
def test_deadlock(self):
82+
"""Regression test for lock inversion between the statusline mutex and
83+
the output mutex."""
84+
self.build()
85+
self.launch(extra_args=["-o", "settings set use-color false"])
86+
self.child.expect("(lldb)")
87+
88+
# Change the terminal dimensions.
89+
terminal_height = 10
90+
terminal_width = 60
91+
self.child.setwinsize(terminal_height, terminal_width)
92+
93+
exe = self.getBuildArtifact("a.out")
94+
95+
self.expect("file {}".format(exe), ["Current executable"])
96+
self.expect("help", ["Debugger commands"])

0 commit comments

Comments
 (0)