[Lldb-commits] [lldb] [lldb][Windows] Fix lldb-server exit deadlock with pipe-mode stdio (PR #227273)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 29 04:10:27 PDT 2026


https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/227273

None

>From 496c4dcc75a8adb08e1ff30933e24ec854a4ab73 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Mon, 28 Sep 2026 21:17:18 +0100
Subject: [PATCH] [lldb][Windows] Fix lldb-server exit deadlock with pipe-mode
 stdio

---
 lldb/include/lldb/Host/windows/PseudoConsole.h            | 8 ++++++++
 lldb/source/Host/windows/PseudoConsole.cpp                | 7 +++++--
 .../Process/Windows/Common/NativeProcessWindows.cpp       | 6 ++++++
 3 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/lldb/include/lldb/Host/windows/PseudoConsole.h b/lldb/include/lldb/Host/windows/PseudoConsole.h
index b98402922bfb3..3580b3d2c78f5 100644
--- a/lldb/include/lldb/Host/windows/PseudoConsole.h
+++ b/lldb/include/lldb/Host/windows/PseudoConsole.h
@@ -61,6 +61,10 @@ class PseudoConsole {
   /// Closes the ConPTY and invalidates its handle, without closing the STDIN
   /// and STDOUT pipes. Closing the ConPTY signals EOF to any process currently
   /// attached to it.
+  ///
+  /// In pipe mode there is no ConPTY to close: this cancels the read pending
+  /// on the STDOUT pipe and marks the pipes closed, so IsConnected() returns
+  /// false from then on, as it does once a ConPTY is closed.
   void Close();
 
   /// Closes the STDIN and STDOUT pipe handles and invalidates them.
@@ -142,6 +146,10 @@ class PseudoConsole {
       reinterpret_cast<HANDLE>(static_cast<intptr_t>(-1));
   HANDLE m_pipe_child_stdout =
       reinterpret_cast<HANDLE>(static_cast<intptr_t>(-1));
+  // Pipe mode: set by Close(). Its CancelIoEx only reaches a read that is
+  // already pending, so a reader has to be able to tell that the pipes were
+  // closed before it starts the next one.
+  std::atomic<bool> m_pipes_closed = false;
   Mode m_mode = Mode::None;
   std::mutex m_mutex{};
   std::condition_variable m_cv{};
diff --git a/lldb/source/Host/windows/PseudoConsole.cpp b/lldb/source/Host/windows/PseudoConsole.cpp
index 3a28adacfea53..0ef3e563a3de6 100644
--- a/lldb/source/Host/windows/PseudoConsole.cpp
+++ b/lldb/source/Host/windows/PseudoConsole.cpp
@@ -176,7 +176,7 @@ llvm::Error PseudoConsole::OpenPseudoConsole(uint16_t req_cols,
 
 bool PseudoConsole::IsConnected() const {
   if (m_mode == Mode::Pipe)
-    return m_conpty_input != INVALID_HANDLE_VALUE &&
+    return !m_pipes_closed && m_conpty_input != INVALID_HANDLE_VALUE &&
            m_conpty_output != INVALID_HANDLE_VALUE;
   return m_conpty_handle != INVALID_HANDLE_VALUE &&
          m_conpty_input != INVALID_HANDLE_VALUE &&
@@ -188,8 +188,10 @@ void PseudoConsole::Close() {
   std::unique_lock<std::mutex> guard(m_mutex);
   if (m_conpty_handle != INVALID_HANDLE_VALUE)
     kernel32.ClosePseudoConsole(m_conpty_handle);
-  if (m_mode == Mode::Pipe && m_conpty_output != INVALID_HANDLE_VALUE)
+  if (m_mode == Mode::Pipe && m_conpty_output != INVALID_HANDLE_VALUE) {
+    m_pipes_closed = true;
     CancelIoEx(m_conpty_output, nullptr);
+  }
   m_conpty_handle = INVALID_HANDLE_VALUE;
   SetStopping(false);
   m_cv.notify_all();
@@ -219,6 +221,7 @@ void PseudoConsole::Reset() {
   Close();
   ClosePseudoConsolePipes();
   CloseAnonymousPipes();
+  m_pipes_closed = false;
   m_mode = Mode::None;
 }
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 849931eaec372..b3470e4817dea 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -954,6 +954,12 @@ void NativeProcessWindows::StopStdioForwarding() {
   if (m_pty)
     m_pty->Close();
 
+  // Close() cancels the read pending on the pipe, but one that the read thread
+  // is about to start would only return at its 5s timeout: EOF cannot come
+  // while the inferior is held at its exit debug event. Wake the thread so that
+  // it sees the closed PTY right away.
+  m_stdio_communication.InterruptRead();
+
   if (m_stdio_communication.ReadThreadIsRunning())
     m_stdio_communication.JoinReadThread();
 



More information about the lldb-commits mailing list