forked from OSchip/llvm-project
[lldb] Synchronize output through the IOHandler
Add synchronization to the IOHandler to prevent multiple threads from writing concurrently to the output or error stream. A scenario where this could happen is when a thread (the default event thread for example) is using the debugger's asynchronous stream. We would delegate this operation to the IOHandler which might be running on another thread. Until this patch there was nothing to synchronize the two at the IOHandler level. Differential revision: https://reviews.llvm.org/D121500
This commit is contained in:
parent
a6ec1e3d79
commit
242c574dc0
|
@ -165,11 +165,14 @@ public:
|
||||||
|
|
||||||
virtual void PrintAsync(const char *s, size_t len, bool is_stdout);
|
virtual void PrintAsync(const char *s, size_t len, bool is_stdout);
|
||||||
|
|
||||||
|
std::mutex &GetOutputMutex() { return m_output_mutex; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Debugger &m_debugger;
|
Debugger &m_debugger;
|
||||||
lldb::FileSP m_input_sp;
|
lldb::FileSP m_input_sp;
|
||||||
lldb::StreamFileSP m_output_sp;
|
lldb::StreamFileSP m_output_sp;
|
||||||
lldb::StreamFileSP m_error_sp;
|
lldb::StreamFileSP m_error_sp;
|
||||||
|
std::mutex m_output_mutex;
|
||||||
repro::DataRecorder *m_data_recorder;
|
repro::DataRecorder *m_data_recorder;
|
||||||
Predicate<bool> m_popped;
|
Predicate<bool> m_popped;
|
||||||
Flags m_flags;
|
Flags m_flags;
|
||||||
|
|
|
@ -154,7 +154,7 @@ using namespace line_editor;
|
||||||
class Editline {
|
class Editline {
|
||||||
public:
|
public:
|
||||||
Editline(const char *editor_name, FILE *input_file, FILE *output_file,
|
Editline(const char *editor_name, FILE *input_file, FILE *output_file,
|
||||||
FILE *error_file, bool color_prompts);
|
FILE *error_file, std::mutex &output_mutex, bool color_prompts);
|
||||||
|
|
||||||
~Editline();
|
~Editline();
|
||||||
|
|
||||||
|
@ -402,7 +402,7 @@ private:
|
||||||
std::string m_suggestion_ansi_suffix;
|
std::string m_suggestion_ansi_suffix;
|
||||||
|
|
||||||
std::size_t m_previous_autosuggestion_size = 0;
|
std::size_t m_previous_autosuggestion_size = 0;
|
||||||
std::mutex m_output_mutex;
|
std::mutex &m_output_mutex;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -655,7 +655,8 @@ private:
|
||||||
const CommandObject::CommandMap &command_map);
|
const CommandObject::CommandMap &command_map);
|
||||||
|
|
||||||
// An interruptible wrapper around the stream output
|
// An interruptible wrapper around the stream output
|
||||||
void PrintCommandOutput(Stream &stream, llvm::StringRef str);
|
void PrintCommandOutput(IOHandler &io_handler, llvm::StringRef str,
|
||||||
|
bool is_stdout);
|
||||||
|
|
||||||
bool EchoCommandNonInteractive(llvm::StringRef line,
|
bool EchoCommandNonInteractive(llvm::StringRef line,
|
||||||
const Flags &io_handler_flags) const;
|
const Flags &io_handler_flags) const;
|
||||||
|
|
|
@ -123,6 +123,7 @@ void IOHandler::SetPopped(bool b) { m_popped.SetValue(b, eBroadcastOnChange); }
|
||||||
void IOHandler::WaitForPop() { m_popped.WaitForValueEqualTo(true); }
|
void IOHandler::WaitForPop() { m_popped.WaitForValueEqualTo(true); }
|
||||||
|
|
||||||
void IOHandler::PrintAsync(const char *s, size_t len, bool is_stdout) {
|
void IOHandler::PrintAsync(const char *s, size_t len, bool is_stdout) {
|
||||||
|
std::lock_guard<std::mutex> guard(m_output_mutex);
|
||||||
lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp;
|
lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp;
|
||||||
stream->Write(s, len);
|
stream->Write(s, len);
|
||||||
stream->Flush();
|
stream->Flush();
|
||||||
|
@ -266,9 +267,9 @@ IOHandlerEditline::IOHandlerEditline(
|
||||||
m_input_sp && m_input_sp->GetIsRealTerminal();
|
m_input_sp && m_input_sp->GetIsRealTerminal();
|
||||||
|
|
||||||
if (use_editline) {
|
if (use_editline) {
|
||||||
m_editline_up = std::make_unique<Editline>(editline_name, GetInputFILE(),
|
m_editline_up = std::make_unique<Editline>(
|
||||||
GetOutputFILE(), GetErrorFILE(),
|
editline_name, GetInputFILE(), GetOutputFILE(), GetErrorFILE(),
|
||||||
m_color_prompts);
|
GetOutputMutex(), m_color_prompts);
|
||||||
m_editline_up->SetIsInputCompleteCallback(
|
m_editline_up->SetIsInputCompleteCallback(
|
||||||
[this](Editline *editline, StringList &lines) {
|
[this](Editline *editline, StringList &lines) {
|
||||||
return this->IsInputCompleteCallback(editline, lines);
|
return this->IsInputCompleteCallback(editline, lines);
|
||||||
|
@ -619,6 +620,7 @@ void IOHandlerEditline::GotEOF() {
|
||||||
void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) {
|
void IOHandlerEditline::PrintAsync(const char *s, size_t len, bool is_stdout) {
|
||||||
#if LLDB_ENABLE_LIBEDIT
|
#if LLDB_ENABLE_LIBEDIT
|
||||||
if (m_editline_up) {
|
if (m_editline_up) {
|
||||||
|
std::lock_guard<std::mutex> guard(m_output_mutex);
|
||||||
lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp;
|
lldb::StreamFileSP stream = is_stdout ? m_output_sp : m_error_sp;
|
||||||
m_editline_up->PrintAsync(stream.get(), s, len);
|
m_editline_up->PrintAsync(stream.get(), s, len);
|
||||||
} else
|
} else
|
||||||
|
|
|
@ -1376,10 +1376,12 @@ Editline *Editline::InstanceFor(EditLine *editline) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Editline::Editline(const char *editline_name, FILE *input_file,
|
Editline::Editline(const char *editline_name, FILE *input_file,
|
||||||
FILE *output_file, FILE *error_file, bool color_prompts)
|
FILE *output_file, FILE *error_file,
|
||||||
|
std::mutex &output_mutex, bool color_prompts)
|
||||||
: m_editor_status(EditorStatus::Complete), m_color_prompts(color_prompts),
|
: m_editor_status(EditorStatus::Complete), m_color_prompts(color_prompts),
|
||||||
m_input_file(input_file), m_output_file(output_file),
|
m_input_file(input_file), m_output_file(output_file),
|
||||||
m_error_file(error_file), m_input_connection(fileno(input_file), false) {
|
m_error_file(error_file), m_input_connection(fileno(input_file), false),
|
||||||
|
m_output_mutex(output_mutex) {
|
||||||
// Get a shared history instance
|
// Get a shared history instance
|
||||||
m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;
|
m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;
|
||||||
m_history_sp = EditlineHistory::GetHistory(m_editor_name);
|
m_history_sp = EditlineHistory::GetHistory(m_editor_name);
|
||||||
|
|
|
@ -2975,8 +2975,12 @@ bool CommandInterpreter::WasInterrupted() const {
|
||||||
return was_interrupted;
|
return was_interrupted;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandInterpreter::PrintCommandOutput(Stream &stream,
|
void CommandInterpreter::PrintCommandOutput(IOHandler &io_handler,
|
||||||
llvm::StringRef str) {
|
llvm::StringRef str,
|
||||||
|
bool is_stdout) {
|
||||||
|
|
||||||
|
lldb::StreamFileSP stream = is_stdout ? io_handler.GetOutputStreamFileSP()
|
||||||
|
: io_handler.GetErrorStreamFileSP();
|
||||||
// Split the output into lines and poll for interrupt requests
|
// Split the output into lines and poll for interrupt requests
|
||||||
const char *data = str.data();
|
const char *data = str.data();
|
||||||
size_t size = str.size();
|
size_t size = str.size();
|
||||||
|
@ -2989,15 +2993,19 @@ void CommandInterpreter::PrintCommandOutput(Stream &stream,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
chunk_size = stream.Write(data, chunk_size);
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(io_handler.GetOutputMutex());
|
||||||
|
chunk_size = stream->Write(data, chunk_size);
|
||||||
|
}
|
||||||
lldbassert(size >= chunk_size);
|
lldbassert(size >= chunk_size);
|
||||||
data += chunk_size;
|
data += chunk_size;
|
||||||
size -= chunk_size;
|
size -= chunk_size;
|
||||||
}
|
}
|
||||||
if (size > 0) {
|
|
||||||
stream.Printf("\n... Interrupted.\n");
|
std::lock_guard<std::mutex> guard(io_handler.GetOutputMutex());
|
||||||
}
|
if (size > 0)
|
||||||
stream.Flush();
|
stream->Printf("\n... Interrupted.\n");
|
||||||
|
stream->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CommandInterpreter::EchoCommandNonInteractive(
|
bool CommandInterpreter::EchoCommandNonInteractive(
|
||||||
|
@ -3033,9 +3041,11 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
|
||||||
// When using a non-interactive file handle (like when sourcing commands
|
// When using a non-interactive file handle (like when sourcing commands
|
||||||
// from a file) we need to echo the command out so we don't just see the
|
// from a file) we need to echo the command out so we don't just see the
|
||||||
// command output and no command...
|
// command output and no command...
|
||||||
if (EchoCommandNonInteractive(line, io_handler.GetFlags()))
|
if (EchoCommandNonInteractive(line, io_handler.GetFlags())) {
|
||||||
|
std::lock_guard<std::mutex> guard(io_handler.GetOutputMutex());
|
||||||
io_handler.GetOutputStreamFileSP()->Printf(
|
io_handler.GetOutputStreamFileSP()->Printf(
|
||||||
"%s%s\n", io_handler.GetPrompt(), line.c_str());
|
"%s%s\n", io_handler.GetPrompt(), line.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StartHandlingCommand();
|
StartHandlingCommand();
|
||||||
|
@ -3057,13 +3067,13 @@ void CommandInterpreter::IOHandlerInputComplete(IOHandler &io_handler,
|
||||||
|
|
||||||
if (!result.GetImmediateOutputStream()) {
|
if (!result.GetImmediateOutputStream()) {
|
||||||
llvm::StringRef output = result.GetOutputData();
|
llvm::StringRef output = result.GetOutputData();
|
||||||
PrintCommandOutput(*io_handler.GetOutputStreamFileSP(), output);
|
PrintCommandOutput(io_handler, output, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now emit the command error text from the command we just executed
|
// Now emit the command error text from the command we just executed
|
||||||
if (!result.GetImmediateErrorStream()) {
|
if (!result.GetImmediateErrorStream()) {
|
||||||
llvm::StringRef error = result.GetErrorData();
|
llvm::StringRef error = result.GetErrorData();
|
||||||
PrintCommandOutput(*io_handler.GetErrorStreamFileSP(), error);
|
PrintCommandOutput(io_handler, error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,7 @@ private:
|
||||||
bool IsInputComplete(lldb_private::Editline *editline,
|
bool IsInputComplete(lldb_private::Editline *editline,
|
||||||
lldb_private::StringList &lines);
|
lldb_private::StringList &lines);
|
||||||
|
|
||||||
|
std::mutex output_mutex;
|
||||||
std::unique_ptr<lldb_private::Editline> _editline_sp;
|
std::unique_ptr<lldb_private::Editline> _editline_sp;
|
||||||
|
|
||||||
PseudoTerminal _pty;
|
PseudoTerminal _pty;
|
||||||
|
@ -117,7 +118,7 @@ EditlineAdapter::EditlineAdapter()
|
||||||
// Create an Editline instance.
|
// Create an Editline instance.
|
||||||
_editline_sp.reset(new lldb_private::Editline(
|
_editline_sp.reset(new lldb_private::Editline(
|
||||||
"gtest editor", *_el_secondary_file, *_el_secondary_file,
|
"gtest editor", *_el_secondary_file, *_el_secondary_file,
|
||||||
*_el_secondary_file, false));
|
*_el_secondary_file, output_mutex, false));
|
||||||
_editline_sp->SetPrompt("> ");
|
_editline_sp->SetPrompt("> ");
|
||||||
|
|
||||||
// Hookup our input complete callback.
|
// Hookup our input complete callback.
|
||||||
|
|
Loading…
Reference in New Issue