Repository navigation
fix(proxy): forward a program's last line without a newline before its exit on Windows (#860) - #869
Merged
Merged
Conversation
…s exit on Windows (#860) On Windows the debuggee of a CodeLLDB-based adapter (rust, cpp, cobol) writes into the adapter's stdio pipes, and GenericAdapterManager splits that stream into lines with a LineBuffer: complete lines are forwarded as they arrive, the trailing fragment on the stream's end/close. CodeLLDB keeps its pipes open until teardown, so a last line printed without a newline was flushed after exited/terminated had been forwarded and the session had stopped listening — `result: 42` never reached get_output. AdapterSpawnResult gains an optional flushStdio(): the early flush of both stdio line buffers, delivering what they hold through onStdioLine marked `{ partial: true }`. The worker keeps it for an adapter that outlives its debuggee and runs it when waitForAdapterStdioDrain settles — on the measured quiet window or the backstop (#856) — before every caller forwards its terminal signal, so the fragment lands ahead of exited on the FIFO IPC channel. The forwarder leaves a partial line without the '\n' it restores for complete lines: the fragment arrives as printed. When the pipes did close, the manager's own close flush ran first and the early flush finds nothing; rdbg (pipes close with the program) is unchanged. Measured on Windows 11 through the dev server, cpp adapter: before, get_output ended at `first line\n`; after, `result: 42` follows 103 ms later (the quiet window), with no added newline, and a 2000-char stdout fragment plus a stderr fragment arrive intact with exit code 3. 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! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On Windows a Rust/C++/COBOL program's last output was lost when it did not end in a newline (#860). The debuggee writes into CodeLLDB's stdio pipes there, and
GenericAdapterManager.consumeStreamsplits that stream with aLineBuffer, flushing the trailing fragment only on the stream'send/close. CodeLLDB keeps its pipes open until teardown, so the fragment was flushed afterexited/terminatedhad been forwarded and the session had stopped listening.How
AdapterSpawnResult.flushStdio?: () => void(optional, so every{ spawn, shutdown }stub returning{ process, pid }keeps type-checking): the early flush of both stdio line buffers, delivering what they hold throughonStdioLinemarked{ partial: true }. Complete lines keep the two-argument call; a laterend/closefinds an emptied buffer.forwardStdio.adapterOutlivesDebuggee, i.e. the rust/cpp/cobol win32 spawn config) and runs it insidewaitForAdapterStdioDrainonce the race settles — on the measured quiet window or the backstop (Windows: the end of a Rust/C++/COBOL program is reported 2 s late — the stdio drain waits for a pipe close CodeLLDB never produces #856). That covers all four terminal callers (adapter exit,exited,terminated,dap_connection_closed) and lands before each forwards its signal, so the fragment is ahead ofexitedon the FIFO IPC channel. When the pipes did close, the manager's own close flush ran first and this finds nothing.buildStdioForwarderleaves a partial line without the'\n'it restores for complete lines: the fragment arrives as printed.consumeStream's comment keeps the Proxy stderr sanitization is per-chunk, not per-line: a secret split across a stream chunk can leak its tail #151 straddle reasoning (never flush on processexitwhile the pipe is live) and explains why the silence-driven flush is different.rdbg (pipes close with the program) is unchanged — covered by a test.
Measured (Windows 11, cpp adapter via the dev server, MSYS2 g++ 15.2.0)
The issue's program (
printf("first line\n"); printf("result: 42");):get_outputafter the launch answeredstopped(exit 0)first line\n— nothing elsefirst line\n, thenresult: 42(no newline added), 103 ms laterA second program with a 2000-char stdout fragment and a stderr fragment (
err-tail), exit code 3: both fragments arrive intact,exitCode: 3.Tests
tests/unit/proxy/dap-proxy-adapter-manager.test.ts: five cases onflushStdio(both streams, partial marker, once-only, nothing pending, log copy redacted like any line).tests/proxy/dap-proxy-worker.test.ts(the Windows: the end of a Rust/C++/COBOL program is reported 2 s late — the stdio drain waits for a pipe close CodeLLDB never produces #856 drain describe): the fragment is forwarded as printed beforeexited; asked for once per drain, after the window; left to the pipe close for the rdbg shape.npm run typecheck:all,npm run lint, proxy/shared unit suites green.Fixes #860
🤖 Generated with Claude Code