Skip to content

Commit a9d5843

Browse files
committed
Fix issues with inferior stdout coming out of order
Summary: We've had a bug where two pieces of code, executing on two threads were attempting to write inferior output simultaneously. The first one was in Debugger::HandleProcessEvent, which handled the cases where stdout was coming while the process was running. The second was in CommandInterpreter::IOHandlerInputComplete, which was ensuring that any output is printed before the command which caused process to run terminates. Both of these things make sense, but the fact they were implemented as two independent functions without any synchronization meant that race conditions could occur (e.g. both threads call process->GetSTDOUT, get two chunks of data, but then end up calling stream->Write in opposite order). This was most apparent in situations where a process quickly writes a bunch of output and then exits (as all our register tests do). This patch adds a mutex to ensure that stdout forwarding happens atomically. It also refactors a code somewhat in order to reduce code duplication. Reviewers: clayborg, jingham Subscribers: jfb, mgorny, lldb-commits Differential Revision: https://reviews.llvm.org/D65152 llvm-svn: 367418
1 parent 5f61690 commit a9d5843

File tree

4 files changed

+32
-92
lines changed

4 files changed

+32
-92
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
345345

346346
void HandleThreadEvent(const lldb::EventSP &event_sp);
347347

348-
size_t GetProcessSTDOUT(Process *process, Stream *stream);
349-
350-
size_t GetProcessSTDERR(Process *process, Stream *stream);
348+
// Ensures two threads don't attempt to flush process output in parallel.
349+
std::mutex m_output_flush_mutex;
350+
void FlushProcessOutput(Process &process, bool flush_stdout,
351+
bool flush_stderr);
351352

352353
SourceManager::SourceFileCache &GetSourceFileCache() {
353354
return m_source_file_cache;

lldb/include/lldb/Interpreter/CommandInterpreter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ class CommandInterpreter : public Broadcaster,
518518

519519
bool IOHandlerInterrupt(IOHandler &io_handler) override;
520520

521-
size_t GetProcessOutput();
521+
void GetProcessOutput();
522522

523523
void SetSynchronous(bool value);
524524

lldb/source/Core/Debugger.cpp

Lines changed: 20 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,60 +1252,23 @@ void Debugger::HandleBreakpointEvent(const EventSP &event_sp) {
12521252
// }
12531253
}
12541254

1255-
size_t Debugger::GetProcessSTDOUT(Process *process, Stream *stream) {
1256-
size_t total_bytes = 0;
1257-
if (stream == nullptr)
1258-
stream = GetOutputFile().get();
1259-
1260-
if (stream) {
1261-
// The process has stuff waiting for stdout; get it and write it out to the
1262-
// appropriate place.
1263-
if (process == nullptr) {
1264-
TargetSP target_sp = GetTargetList().GetSelectedTarget();
1265-
if (target_sp)
1266-
process = target_sp->GetProcessSP().get();
1267-
}
1268-
if (process) {
1269-
Status error;
1270-
size_t len;
1271-
char stdio_buffer[1024];
1272-
while ((len = process->GetSTDOUT(stdio_buffer, sizeof(stdio_buffer),
1273-
error)) > 0) {
1274-
stream->Write(stdio_buffer, len);
1275-
total_bytes += len;
1276-
}
1277-
}
1278-
stream->Flush();
1279-
}
1280-
return total_bytes;
1281-
}
1282-
1283-
size_t Debugger::GetProcessSTDERR(Process *process, Stream *stream) {
1284-
size_t total_bytes = 0;
1285-
if (stream == nullptr)
1286-
stream = GetOutputFile().get();
1287-
1288-
if (stream) {
1289-
// The process has stuff waiting for stderr; get it and write it out to the
1290-
// appropriate place.
1291-
if (process == nullptr) {
1292-
TargetSP target_sp = GetTargetList().GetSelectedTarget();
1293-
if (target_sp)
1294-
process = target_sp->GetProcessSP().get();
1295-
}
1296-
if (process) {
1297-
Status error;
1298-
size_t len;
1299-
char stdio_buffer[1024];
1300-
while ((len = process->GetSTDERR(stdio_buffer, sizeof(stdio_buffer),
1301-
error)) > 0) {
1302-
stream->Write(stdio_buffer, len);
1303-
total_bytes += len;
1304-
}
1305-
}
1306-
stream->Flush();
1307-
}
1308-
return total_bytes;
1255+
void Debugger::FlushProcessOutput(Process &process, bool flush_stdout,
1256+
bool flush_stderr) {
1257+
const auto &flush = [&](Stream &stream,
1258+
size_t (Process::*get)(char *, size_t, Status &)) {
1259+
Status error;
1260+
size_t len;
1261+
char buffer[1024];
1262+
while ((len = (process.*get)(buffer, sizeof(buffer), error)) > 0)
1263+
stream.Write(buffer, len);
1264+
stream.Flush();
1265+
};
1266+
1267+
std::lock_guard<std::mutex> guard(m_output_flush_mutex);
1268+
if (flush_stdout)
1269+
flush(*GetAsyncOutputStream(), &Process::GetSTDOUT);
1270+
if (flush_stderr)
1271+
flush(*GetAsyncErrorStream(), &Process::GetSTDERR);
13091272
}
13101273

13111274
// This function handles events that were broadcast by the process.
@@ -1345,15 +1308,9 @@ void Debugger::HandleProcessEvent(const EventSP &event_sp) {
13451308
pop_process_io_handler);
13461309
}
13471310

1348-
// Now display and STDOUT
1349-
if (got_stdout || got_state_changed) {
1350-
GetProcessSTDOUT(process_sp.get(), output_stream_sp.get());
1351-
}
1352-
1353-
// Now display and STDERR
1354-
if (got_stderr || got_state_changed) {
1355-
GetProcessSTDERR(process_sp.get(), error_stream_sp.get());
1356-
}
1311+
// Now display STDOUT and STDERR
1312+
FlushProcessOutput(*process_sp, got_stdout || got_state_changed,
1313+
got_stderr || got_state_changed);
13571314

13581315
// Give structured data events an opportunity to display.
13591316
if (got_structured_data) {

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,32 +2664,14 @@ void CommandInterpreter::UpdateExecutionContext(
26642664
}
26652665
}
26662666

2667-
size_t CommandInterpreter::GetProcessOutput() {
2668-
// The process has stuff waiting for stderr; get it and write it out to the
2669-
// appropriate place.
2670-
char stdio_buffer[1024];
2671-
size_t len;
2672-
size_t total_bytes = 0;
2673-
Status error;
2667+
void CommandInterpreter::GetProcessOutput() {
26742668
TargetSP target_sp(m_debugger.GetTargetList().GetSelectedTarget());
2675-
if (target_sp) {
2676-
ProcessSP process_sp(target_sp->GetProcessSP());
2677-
if (process_sp) {
2678-
while ((len = process_sp->GetSTDOUT(stdio_buffer, sizeof(stdio_buffer),
2679-
error)) > 0) {
2680-
size_t bytes_written = len;
2681-
m_debugger.GetOutputFile()->Write(stdio_buffer, bytes_written);
2682-
total_bytes += len;
2683-
}
2684-
while ((len = process_sp->GetSTDERR(stdio_buffer, sizeof(stdio_buffer),
2685-
error)) > 0) {
2686-
size_t bytes_written = len;
2687-
m_debugger.GetErrorFile()->Write(stdio_buffer, bytes_written);
2688-
total_bytes += len;
2689-
}
2690-
}
2691-
}
2692-
return total_bytes;
2669+
if (!target_sp)
2670+
return;
2671+
2672+
if (ProcessSP process_sp = target_sp->GetProcessSP())
2673+
m_debugger.FlushProcessOutput(*process_sp, /*flush_stdout*/ true,
2674+
/*flush_stderr*/ true);
26932675
}
26942676

26952677
void CommandInterpreter::StartHandlingCommand() {

0 commit comments

Comments
 (0)