[Lldb-commits] [lldb] [lldb][windows] drain the ConPTY on process exit (PR #196371)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Fri May 8 00:20:54 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/196371
>From 7f34a5d7be06a9b0e3fc77f144a189bbd01b33a3 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 7 May 2026 18:06:19 +0100
Subject: [PATCH 1/2] [lldb][windows] drain the ConPTY on process exit
---
.../Process/Windows/Common/ProcessWindows.cpp | 59 ++++++++++---------
.../Process/Windows/Common/ProcessWindows.h | 4 ++
2 files changed, 34 insertions(+), 29 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 28b5554069c90..9dd49a9e7a1ba 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -655,6 +655,7 @@ void ProcessWindows::OnExitProcess(uint32_t exit_code) {
LLDB_LOG(log, "Process {0} exited with code {1}", GetID(), exit_code);
if (m_pty) {
+ DrainProcessStdout();
m_pty->SetStopping(true);
m_pty->Close();
m_stdio_communication.InterruptRead();
@@ -675,6 +676,32 @@ void ProcessWindows::OnExitProcess(uint32_t exit_code) {
ProcessDebugger::OnExitProcess(exit_code);
}
+void ProcessWindows::DrainProcessStdout() {
+ 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;) {
+ 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;
+ m_stdio_communication.SynchronizeWithReadThread();
+ } else {
+ ++consec_empty;
+ if (consec_empty < 3)
+ ::SleepEx(1, FALSE);
+ }
+ }
+}
+
void ProcessWindows::OnDebuggerConnected(lldb::addr_t image_base) {
DebuggerThreadSP debugger = m_session_data->m_debugger;
Log *log = GetLog(WindowsLog::Process);
@@ -746,36 +773,10 @@ ProcessWindows::OnDebugException(bool first_chance,
// 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;) {
- 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;
- 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();
+ DrainProcessStdout();
SetPrivateState(eStateStopped);
}
@@ -796,12 +797,12 @@ ProcessWindows::OnDebugException(bool first_chance,
LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
record.GetExceptionAddress());
}
- drain_stdout();
+ DrainProcessStdout();
SetPrivateState(eStateStopped);
break;
case EXCEPTION_SINGLE_STEP:
result = ExceptionResult::BreakInDebugger;
- drain_stdout();
+ DrainProcessStdout();
SetPrivateState(eStateStopped);
break;
default:
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index 228619d0e3d5e..31cf498a16d50 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -108,6 +108,10 @@ class ProcessWindows : public Process, public ProcessDebugger {
void SetPseudoConsoleHandle() override;
protected:
+ /// Block until the stdio read thread has surfaced everything currently
+ /// buffered in the ConPTY/pipe to the process's STDOUT cache.
+ void DrainProcessStdout();
+
ProcessWindows(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp);
Status DoGetMemoryRegionInfo(lldb::addr_t vm_addr,
>From 3c07746a7c45367c36202b9b0dc3646bdb17e946 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Fri, 8 May 2026 08:20:38 +0100
Subject: [PATCH 2/2] fixup! [lldb][windows] drain the ConPTY on process exit
---
.../Plugins/Process/Windows/Common/ProcessWindows.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 9dd49a9e7a1ba..eb1c4acc0f2bb 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -768,11 +768,6 @@ 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.
if (!first_chance) {
// Not any second chance exception is an application crash by definition.
// It may be an expression evaluation crash.
@@ -797,6 +792,11 @@ ProcessWindows::OnDebugException(bool first_chance,
LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
record.GetExceptionAddress());
}
+ // 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.
DrainProcessStdout();
SetPrivateState(eStateStopped);
break;
More information about the lldb-commits
mailing list