Index: include/lldb/Core/Debugger.h =================================================================== --- include/lldb/Core/Debugger.h +++ include/lldb/Core/Debugger.h @@ -204,6 +204,26 @@ bool CheckTopIOHandlerTypes(IOHandler::Type top_type, IOHandler::Type second_top_type); + //------------------------------------------------------------------ + /// Guard class that delays the output printing from the given debugger + /// until the end of the scope. Useful if you aquire any IOHandler locks + /// in the current scope and also call code that might print via an IOHandler + /// (which could lead to deadlocks). + /// + /// This guard can be used in nested scopes. Multiple guards on the + /// same debugger behave the same as if only the top-most guard + /// requested to delay the messages. + /// + /// @see EnableDelayedPrinting() and TryFlushingDelayedMessages(). + //------------------------------------------------------------------ + struct MessageDelayScope { + Debugger &m_debugger; + MessageDelayScope(Debugger &d) : m_debugger(d) { + m_debugger.EnableDelayedPrinting(); + } + ~MessageDelayScope() { m_debugger.TryFlushingDelayedMessages(); } + }; + void PrintAsync(const char *s, size_t len, bool is_stdout); ConstString GetTopIOHandlerControlSequence(char ch); @@ -417,6 +437,74 @@ // object Debugger(lldb::LogOutputCallback m_log_callback, void *baton); + //------------------------------------------------------------------ + /// A message sent via PrintAsync that we store to delay the actual + /// call until later. + //------------------------------------------------------------------ + struct DelayedMessage { + DelayedMessage(std::string data, bool for_stdout) + : m_data(data), m_for_stdout(for_stdout) {} + std::string m_data; + bool m_for_stdout; + }; + + //------------------------------------------------------------------ + /// Starts delaying any calls to PrintAsync until a matching call + /// to FlushDelayedMessages occurs. This function should only be + /// called before a matching call to FlushDelayedMessages. + /// + /// This method is intentionally private. Use MessageDelayScope to + /// call this method (and the matching FlushDelayedMessage()) method + /// below. + //------------------------------------------------------------------ + void EnableDelayedPrinting() { + std::lock_guard guard(m_delayed_output_mutex); + ++m_delayed_output_counter; + } + + //------------------------------------------------------------------ + /// Tries to flush any delayed messages to PrintAsync. This might + /// not be possible if it was requested by multiple users to + /// delay messages and not all have given consent at this point for + /// forwarding the messages. + //------------------------------------------------------------------ + void TryFlushingDelayedMessages() { + // We copy the buffer here. We do *not* want to call PrintAsync (or + // actually anything else) while we own this lock. + std::vector buffer_to_send; + bool should_print_delayed = false; + { + std::lock_guard guard(m_delayed_output_mutex); + buffer_to_send = std::move(m_delayed_output); + m_delayed_output.clear(); + --m_delayed_output_counter; + // 0 means that all users we have have consented that we can now foward + // our delayed messages to the IOHandlers. + if (m_delayed_output_counter == 0) + should_print_delayed = true; + } + if (!should_print_delayed) + return; + // Forward all cached messages to the IOHandlers. + for (const auto &msg : buffer_to_send) { + PrintAsync(msg.m_data.data(), msg.m_data.size(), msg.m_for_stdout); + } + } + + /// This mutex protects *only* m_async_buffer and m_should_buffer_async. + std::mutex m_delayed_output_mutex; + /// The list of delayed messages in the order in which they arrived. + /// @note Protected by m_delayed_output_mutex. + std::vector m_delayed_output; + /// This counter keeps track of how many users have requested that we delay + /// the forwarding of calls to PrintAsync to the IOHandlers. If the counter + /// is 0, then it's safe to directly forward all calls to PrintAsync to the + /// IOHandlers. If the counter is *not* 0, then all calls to PrintAsync + /// should be cached until the counter is 0 again. + /// @see EnableDelayedPrinting() and TryFlushingDelayedMessages(). + /// @note Protected by m_delayed_output_mutex. + unsigned m_delayed_output_counter = 0; + DISALLOW_COPY_AND_ASSIGN(Debugger); }; Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -985,6 +985,21 @@ } void Debugger::PrintAsync(const char *s, size_t len, bool is_stdout) { + bool should_forward = false; + { + // We check if any user requested to delay output to a later time + // (which is designated by m_delayed_output_counter being not 0). + std::lock_guard guard(m_delayed_output_mutex); + if (m_delayed_output_counter != 0) { + // We want to delay messages, so push them to the buffer. + m_delayed_output.emplace_back(std::string(s, len), is_stdout); + } else { + // Allow direct forwarding to the IOHandlers. + should_forward = true; + } + } + if (!should_forward) + return; lldb::StreamFileSP stream = is_stdout ? GetOutputFile() : GetErrorFile(); m_input_reader_stack.PrintAsync(stream.get(), s, len); } Index: source/Core/IOHandler.cpp =================================================================== --- source/Core/IOHandler.cpp +++ source/Core/IOHandler.cpp @@ -348,6 +348,15 @@ } bool IOHandlerEditline::GetLine(std::string &line, bool &interrupted) { + // For safety reasons let's delay all messages that might be printed + // to the output until we are done with this method. Printing to the + // standard output while we are working with EditLine could cause + // problems. Especially any held locks that are needed for printing + // to the console are problematic. We might take those locks here + // because we want to do console output and they might be requested by any + // lldb code that will be called from here (possibly in another thread). + Debugger::MessageDelayScope buffer_scope(m_debugger); + #ifndef LLDB_DISABLE_LIBEDIT if (m_editline_ap) { return m_editline_ap->GetLine(line, interrupted);