Repository navigation
fix(windows): report the end of a Rust/C++/COBOL program when it happens, not 2 s later (#856) - #859
Conversation
…ens, not 2 s later The worker holds exited/terminated until the adapter's forwarded stdio has drained, and took "the pipes closed" as the sign. CodeLLDB outlives its debuggee and keeps the pipes open until teardown, so on Windows (where the debuggee writes straight into them) the wait ran to its 2 s backstop at the end of every program. - AdapterSpawnConfig.forwardStdio gains adapterOutlivesDebuggee; buildLldbSpawnConfig sets it on win32 (rust, cpp, cobol). - For such an adapter the drain also settles when the pipes have been quiet for 100 ms, counted from the later of the last chunk and the first terminal signal, and re-checked after one full turn of the event loop so a descheduled worker cannot take unread output for silence. Output still arriving keeps the wait open; the 2 s backstop is unchanged. - rdbg, whose pipes close with the program, still waits for the close. Measured: end reported 110-134 ms after launch (was 2009-2030 ms); the longest gap inside 9 MB of exit-time output was 14 ms idle, 39 ms with every core busy. Closes #856. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
… endings Review follow-up on #859. The comment on trackStdioActivity said a counted chunk's lines have been forwarded; that holds for complete lines only (a trailing fragment waits in the line buffer for the pipe to close, #860). The drain's doc stated the output-before-exit order as a guarantee; it is one when the wait ends on the close, a measured expectation when it ends on silence, and neither at the backstop. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Code reviewFive reviewers (CLAUDE.md compliance, bug scan, git history of the exit path, earlier PR feedback, code comments) went over cb1c9b1. No bugs found. The history reviewer traced the earlier exit-ordering fixes (#222, #247, #258, #366, #720, #746, #753, #763) against the shorter wait and found none of them relied on the 2 s gap for CodeLLDB. One reviewer found two comments that claimed more than the code guarantees; both are corrected in 75a33ff (comments only):
mcp-debugger/src/proxy/dap-proxy-worker.ts Lines 2436 to 2444 in 75a33ff
mcp-debugger/src/proxy/dap-proxy-worker.ts Lines 2455 to 2476 in 75a33ff Following up on the first point: a program's last output is lost on Windows for the CodeLLDB languages when it does not end in a newline. Checked live — it is lost on main as well, so it is not this PR's doing, and it is filed as #860 rather than folded in here. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
…ad cancel flag goes Output that lands in the same instant the quiet window closes must restart the window: a test now fails if the turn after the timer stops checking for it (verified by removing the check). The cancelled flag in waitForAdapterStdioQuiet could never be read — at most one of the timer and the immediate is pending, and cancel clears both — so it is removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
One more commit after the review above, 86d4697: the quiet window's event-loop turn now has a test of its own (output arriving in the same instant the window closes must restart it — verified by removing the re-check and watching the test fail), and the CI on this head has not run: GitHub Actions is in an outage and the hosted runners never picked the jobs up ("The job was not acquired by Runner of type hosted even after multiple attempts"). The first head, cb1c9b1, passed Build and Test on ubuntu and Windows, Lint and Container Tests. |
Closes #856.
What was wrong
On Windows, every Rust, C/C++ and COBOL session reported the end of its program 2 s after the program ended. The proxy holds
exited/terminateduntil the output the program printed on its way out has been forwarded (#222), and took "the adapter's stdio pipes closed" as the sign that it had. That is right forrdbg -c, which exits with its debuggee. CodeLLDB outlives its debuggee and keeps the pipes open until the session is torn down, so on Windows — where the debuggee writes straight into those pipes (#223) — the wait ran to its 2 s backstop at the end of every program.It predates #857, but #857 made it visible: with a 1 s launch hold, a short Rust/C++/COBOL program on Windows answered
running+pending: truealthough it had already ended.The fix
A policy can now say its adapter outlives the debuggee (
forwardStdio.adapterOutlivesDebuggee;buildLldbSpawnConfigsets it on win32, so Rust, C/C++ and COBOL get it). For such an adapter the worker also takes the pipes going quiet as "drained": when the adapter reports the exit the process is gone and everything it wrote is already in the pipe, so the proxy only has to read what is buffered.terminatedqueued behindexiteddoes not wait a second window.setImmediateruns after the poll phase that reads pending I/O) and starts the window over if that turn brought a chunk. A worker that was descheduled cannot mistake unread output for silence.rdbgis untouched: its pipes close with the program, and silence there is not the end (anat_exithook can still print).Sizing the window
Measured on CodeLLDB/Windows with the proxy logging every line at debug level, as the gap between consecutive chunks of output that arrived after
exited:exitedexitedexited100 ms is 7× the longest idle gap and 2.5× the longest under load. (The same table shows why "wait a fixed short time after
exited" would not do: output can keep arriving for a second or two. The window is on silence, not on elapsed time.)Result
End reported after launch, hello-world example, three runs each (same method as the table in #857):
start_debuggingon all three now answersstoppedwith the exit code in the one call. The 9 MB and 6 MB programs were re-run on the fixed build: the session's output ends with their last line (line 199999 …,line 119999 …) in every run.Tests
tests/proxy/dap-proxy-worker.test.ts, new block:exitedis forwarded once the pipes have been quiet, not at the backstop; late output keeps the wait open and is forwarded first; output that lands in the same instant the window closes restarts it (this one was mutation-checked: it fails when the event-loop turn stops re-checking); pipes that never go quiet end at the backstop;terminateddoes not wait a second window; a close still settles at once; an adapter without the flag still waits for the close.tests/e2e/mcp-server-smoke-cpp.test.ts: a short program's launch answersstopped, exit code 0, with both of its output lines. Fails on Windows before the fix (running, "nothing is armed"), passes after.typecheck:all.🤖 Generated with Claude Code