Repository navigation
Conversation
The renderer acked each PTY batch on IPC receipt, before xterm's asynchronous parser had consumed it, so host-side backpressure never engaged and xterm's queue grew without bound. Past xterm's 50M-char discard mark write threw before the ack, stranding that chunk's credit and leaving the PTY paused. - TerminalEngine.write takes an optional onParsed credit callback; XtermEngine passes it to xterm's write callback, releases pre-attach output on receipt, and releases still-queued chunks on dispose (xterm drops their callbacks). - terminal/output-dispatch.ts acks exactly once per batch: when the engine releases it, at once without an engine, or after a write that throws. - Tests: main's real IPC adapter driven through the dispatcher with a late-parsing engine (pause engages; a thrown write still resumes the PTY), dispatcher and engine unit tests, and terminal-smoke check 14 (yes | head -c 300000000, then a shell-computed marker).
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A 300M-char burst must be parsed in full before any marker after it can show, fix or no fix, and outlasted 240 s on the CI runner. Interrupting a 5 s yes burst and timing the next command's output tests what the fix changes: how much output is still queued when the burst stops.
…op ack - registry: dispose every engine on pagehide, so a renderer reload returns the credit of chunks xterm had queued (VC-425's leak class) instead of leaving their PTYs paused; tested. - xterm-engine: a throwing ack inside xterm's parse loop is logged, not thrown, so it cannot wedge xterm's write queue; dispose relies on it. - output-dispatch: note that acks carry no attachment generation (a late ack can only resume early, never freeze, thanks to the host's clamp). - tests: restore the console.error spy, rename the swapped first/second. - terminal-smoke check 14: state that it guards against a stall rather than proving the old regression.
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.
Problem
The renderer acked each PTY output batch when the IPC event arrived (
sessions-layer.tsx), before xterm had parsed it. xterm'swriteis asynchronous: it queues the chunk and parses it in time slices. So:yes/cat bigfilekept sending 256K batches every 8 ms while xterm's queue grew without bound.write data discarded…fromwrite(). That throw happened on the line beforeack(...), so the chunk's credit was lost. Main's unacked count stayed above the 5K low watermark, the PTY stayed paused, and the pane stayed frozen until it was closed.Fix
TerminalEngine.write(data, onParsed?): the engine callsonParsedonce it no longer holds the chunk.XtermEnginepasses it as xterm's write callback, which fires after parse. Chunks still queued when the engine is disposed are released bydispose, because xterm drops queued callbacks without calling them. A disposed engine releases a late write at once.terminal/output-dispatch.ts(deliverTerminalOutput), used by the sessions layer. It acks exactly once per delivered batch:catchifwritethrows (the error is logged, not rethrown).terminal/registry.tsdisposes every engine onpagehide. A renderer reload tears xterm down without running its queued parse callbacks, and main does not treat a reload as a detach. Without this, up to a watermark plus a batch of credit would be stranded per busy PTY, leaving it paused (the VC-425 leak class, made wider by acking after parse).disposenow releases that credit while the page can still send it.Decisions
onParsedfor a write that throws. The dispatcher acks in itscatch, and its once-guard makes a double ack impossible either way.yes | head -c 300000000; echo done. The first push used the literal form, and check 14 failed on both CI attempts: after 240 s the pane still showedyrows, with no discard error. A marker after a fixed-size burst can only show once xterm has parsed every char of the burst, fix or no fix. The fix does not change how fast xterm parses; it changes how much is queued. So the smoke measures that instead: after Ctrl-C, the next command's output appears within moments when flow control holds xterm's queue to a few hundred K. With ack-on-receipt it waits behind tens of MB, or never shows if the PTY is stuck. A stall in the new path would also fail this check.setTimeout-driven parse, and production windows usebackgroundThrottling: true(quiet-windows.ts:38→index.ts:576). A minimized or covered window may therefore limit a busy PTY's throughput to about one parse slice per second until it is shown again. This is reasoned, not measured, and VS Code works the same way. Follow-up to measure it: VC-859.output-dispatch.tsis enrolled in the desktop 100% coverage gate: a branch that loses an ack fails silently and permanently.Tests
apps/desktop/src/main/pty.test.ts, new block "flow control through the renderer's output dispatch". It drives main's real IPC adapter (volli:terminal-dataout,volli:terminal-ackin) through the real dispatcher with an engine that parses late:yes-style burst,pty.pause()is called; after the engine parses everything, the PTY resumes.pty.pausecalled 0 times). Single-file run,--maxWorkers=1.terminal/output-dispatch.test.ts: one ack after release, immediate ack with no engine, ack plus log on throw, no double ack.terminal/xterm-engine.test.ts: release only on xterm's parse callback, pre-attach release on receipt, release of queued chunks on dispose (each release isolated), no release of a refused write.terminal/registry.test.ts:pagehidedisposes and forgets every engine, even past one whosedisposethrows.e2e/terminal-smoke.mjscheck 14 (runs in CI): runyesfor 5 s, Ctrl-C it, thenecho "flow-$((6 * 7))-done".flow-42-donemust appear in the pane within 30 s, with nowrite data discardedconsole error. The detail reportsdrainMs. This check guards the ack-after-parse path against a stall; it does not by itself prove the old regression. The old code also passes it whenever xterm keeps up with 5 s ofyes. Thepty.test.tstests above are the regression proof. The marker is computed by the shell, so the typed command line can't satisfy the wait.Local runs, each through the machine's serial runner with
--maxWorkers=1, one file at a time after review:pty.test.ts(226 passed),registry.test.ts(6 passed),xterm-engine.test.ts(12 passed),output-dispatch.test.ts(4 passed, before review), andvp checkon every touched file. The smoke was not run locally (CI only).Overlap:
git merge-treeis clean againstorigin/mainand against the heads of #909, #919, #920, #921 and #922. Targets main.Review
From the review of 247936f (ready with nits, no must-fix). Addressed in b0ac31d:
pagehidedisposes every engine, so a reload returns queued credit. Comment references VC-425; test inregistry.test.ts. I took the renderer-side option, not a main-sidereleaseFlowon navigation: it reuses thedisposerelease this PR already added and needs no new IPC.drainMs < 5000gate: this is a core e2e smoke, so a timing gate would risk flakes on a gating lane, anddrainMsstays in the detail.console.errorspy inpty.test.tsis restored in afinally.first/secondnames are nowthrowing/plain.onParsedcall inside xterm's parse loop is wrapped in try/console.warn, with a new test.disposerelies on it.output-dispatch.tsnow notes that acks carry no attachment generation: the clamp means a late ack can only cause an early resume, never a freeze.