[Lldb-commits] [lldb] [lldb][windows] fix a race condition in IO reader thread (PR #194422)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Tue Apr 28 04:46:41 PDT 2026


https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/194422

>From 5107563149657ad292593452bf6a57228cfe9e83 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Mon, 27 Apr 2026 18:37:32 +0100
Subject: [PATCH 1/2] [lldb][windows] fix a race condition in IO reader thread

---
 .../Process/Windows/Common/ProcessWindows.cpp | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index c0c8be8bd62b8..88e6740067649 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -740,9 +740,39 @@ ProcessWindows::OnDebugException(bool first_chance,
     return ExceptionResult::SendToApplication;
   }
 
+  // Drain any in-flight process output before announcing the stop. The I/O
+  // reader thread and this debug-event thread run concurrently. Without
+  // synchronization the eBroadcastBitStateChanged(Stopped) event can reach
+  // the Debugger event thread before the preceding eBroadcastBitSTDOUT
+  // events.
+  auto drain_stdout = [this] {
+    if (!m_stdio_communication.ReadThreadIsRunning())
+      return;
+    m_stdio_communication.SynchronizeWithReadThread();
+    if (!m_pty || m_pty->GetMode() != PseudoConsole::Mode::ConPTY)
+      return;
+
+    HANDLE pipe = m_pty->GetSTDOUTHandle();
+    for (int consec_empty = 0; consec_empty < 3;) {
+      DWORD avail = 0;
+      if (!::PeekNamedPipe(pipe, nullptr, 0, nullptr, &avail, nullptr))
+        break;
+      if (avail > 0) {
+        consec_empty = 0;
+        if (m_stdio_communication.ReadThreadIsRunning())
+          m_stdio_communication.SynchronizeWithReadThread();
+      } else {
+        ++consec_empty;
+        if (consec_empty < 3)
+          ::SleepEx(1, FALSE);
+      }
+    }
+  };
+
   if (!first_chance) {
     // Not any second chance exception is an application crash by definition.
     // It may be an expression evaluation crash.
+    drain_stdout();
     SetPrivateState(eStateStopped);
   }
 
@@ -763,10 +793,12 @@ ProcessWindows::OnDebugException(bool first_chance,
       LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
                record.GetExceptionAddress());
     }
+    drain_stdout();
     SetPrivateState(eStateStopped);
     break;
   case EXCEPTION_SINGLE_STEP:
     result = ExceptionResult::BreakInDebugger;
+    drain_stdout();
     SetPrivateState(eStateStopped);
     break;
   default:

>From 0c51a710a2665054275b95e21cba83b5b249e10d Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Tue, 28 Apr 2026 12:46:29 +0100
Subject: [PATCH 2/2] break on not running read thread

---
 .../Plugins/Process/Windows/Common/ProcessWindows.cpp       | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 88e6740067649..73374a7c07bde 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -754,13 +754,15 @@ ProcessWindows::OnDebugException(bool first_chance,
 
     HANDLE pipe = m_pty->GetSTDOUTHandle();
     for (int consec_empty = 0; consec_empty < 3;) {
+      if (!m_stdio_communication.ReadThreadIsRunning())
+        break;
       DWORD avail = 0;
+      // PeekNamedPipe is thread safe.
       if (!::PeekNamedPipe(pipe, nullptr, 0, nullptr, &avail, nullptr))
         break;
       if (avail > 0) {
         consec_empty = 0;
-        if (m_stdio_communication.ReadThreadIsRunning())
-          m_stdio_communication.SynchronizeWithReadThread();
+        m_stdio_communication.SynchronizeWithReadThread();
       } else {
         ++consec_empty;
         if (consec_empty < 3)



More information about the lldb-commits mailing list