Conversation
There was a problem hiding this comment.
[P2] Bound terminal priority so background work can progress
Continuously queued terminal input and its urgent redraws can starve background completions and ticks. Bound real-input priority while preserving safety and deferred-submit ordering, avoid urgent redraws for ignored mouse events, and test background progress while input remains queued.
| cx, shutdown.as_mut(), stopped.as_mut(), &mut events, | ||
| &frames, &mut submit_after_paste, | ||
| ) { | ||
| return Poll::Ready(match event { |
There was a problem hiding this comment.
[P2] Bound terminal priority so background work can progress
When terminal input remains queued—for example, mouse motion or dragging while terminal drawing is slow—this early return prevents the background round-robin from being polled. Every terminal event also calls frames.invalidate_input(), so the next iteration selects an urgent frame; execution can alternate between input and drawing without processing worker completions, model-switch completions, voice events, or ticks until input drains. Mouse capture is enabled, and even ignored mouse movement requests an urgent frame. Keep shutdown, stop, and deferred-submit ordering, but bound consecutive real terminal events before allowing background progress; avoid urgent redraws for ignored mouse events as well. The priority test checks background eligibility only after input empties, so it does not cover sustained-input progress.
For agents:
Validate the following issue, address if needed:
<comment>Continuously queued terminal events and their urgent frames can indefinitely bypass background polling; bound real-input priority while preserving safety and deferred-submit ordering, avoid redraws for ignored mouse events, and cover background progress with input still queued.</comment>
<file_context>
+ if let Poll::Ready(event) = scheduler::poll_priority(
+ cx, shutdown.as_mut(), stopped.as_mut(), &mut events,
+ &frames, &mut submit_after_paste,
+ ) {
+ return Poll::Ready(match event {
+ scheduler::PriorityEvent::Shutdown => SessionEvent::StorageShutdown,
+ scheduler::PriorityEvent::Stop => SessionEvent::Stop,
+ scheduler::PriorityEvent::Frame => SessionEvent::Frame,
+ scheduler::PriorityEvent::Terminal(event) => SessionEvent::Terminal(event),
+ });
+ }
</file_context>
|
Final reader/lifecycle follow-up and measurements:
Controlled PTY comparison1,000-message replay followed by paced streaming, 120×40 PTY, two sequential rounds with reversed ordering, 200 pooled keys per mode. No compilation overlapped the final timed runs. All variants used the same probe and complete capability/cursor replies.
Streaming output across the two runs: baseline 896,089 bytes / 6,536 completed frames; existing PR 152,198 / 541; final 180,071 / 600. These are observations of the timed windows, not fixed-work throughput scores. Streaming tail latency improved substantially in this experiment; idle latency did not. Earlier comparisons also changed baseline/current ordering. This does not establish a reliably reproduced user regression or a universal responsiveness fix. The endpoint is key write → glyph bytes and synchronized-frame end at the PTY master, not physical terminal paint. Remaining limits: Windows runtime is untested; query-only Windows image protocols now rely on environment/window-size fallback. OS thread-spawn exhaustion and a returned worker I/O error were audited, not dynamically injected. The dependency's pre-existing tmux setup subprocess can still block outside the bounded terminal-query interval. Abort builds cannot promise joined cleanup. Lifecycle tests cover real shared terminal/auth boundaries, not every full startup-picker/main-loop cancellation path. |
There was a problem hiding this comment.
Continuously queued terminal input and urgent redraws can still starve background completions and ticks (existing P2 thread remains valid). The latency probe can silently include idle samples in its hot percentiles after its finite streaming fixture ends (P3).
| drain(0.1) | ||
| hot = samples("hot") | ||
| raw.flush() | ||
| if b"stream" not in (output / "terminal.bin").read_bytes(): |
There was a problem hiding this comment.
[P3] Keep streaming active throughout hot latency sampling
The fixture emits only 6,000 chunks with 5 ms sleeps before becoming idle, but --samples has no corresponding duration bound. For example, --samples 10000 spends at least 300 seconds in the per-sample drains alone, so on a normally progressing run much of the reported hot distribution measures an idle TUI after streaming ends. This check accepts any earlier rendered stream text and cannot detect that contamination. Keep the fixture streaming until hot sampling explicitly stops it, or detect stream completion and fail or truncate the hot measurement correctly; cover a measurement that outlasts the fixture.
For agents:
Validate the following issue, address if needed:
<comment>The finite streaming fixture can end before hot sampling completes, silently mixing idle latency into hot percentiles; coordinate stream lifetime with sampling or detect completion and fail or truncate, and cover measurements that outlast the fixture.</comment>
<file_context>
+ hot = samples("hot")
+ raw.flush()
+ if b"stream" not in (output / "terminal.bin").read_bytes():
+ raise RuntimeError("no replay stream was rendered during hot measurements")
</file_context>
Summary
Prioritize TUI input feedback while reducing streaming latency through ordered update batches, paced background frames, completed-row reuse, and fewer Markdown suffix scans. Give each active terminal interval one joined input reader, including the startup picker and authentication handoffs. Turn completion and background-call enumeration operate on active calls rather than the historical transcript.
Technical details
Input ownership and terminal lifecycle
Replace EventStream and synchronous UI polling with one OS reader feeding a bounded 256-event channel. Full queues backpressure rather than drop typing; model/session changes retain the reader and queued input. Returned I/O errors are delivered once before disconnection.
Capability queries finish before reader creation. Authentication, normal exit, cancellation, and unwind close the receiver and join input before terminal restoration or child authentication. A terminal-mode guard also restores failed setup; panic-abort builds retain best-effort restoration without a join guarantee. Initial entry does not require a cursor-position report; resume still clears retained terminal buffers.
Replace the image library's detached query thread with synchronous nonblocking Unix capability I/O, bounded by a 150 ms monotonic deadline and a 4,096-byte work budget. macOS uses select for the controlling-terminal alias; other Unix platforms use poll. Native image support requires known font dimensions. Windows skips query-only protocol detection and uses environment/window-size fallback. The dependency's existing synchronous tmux passthrough subprocess remains outside the I/O deadline, so terminal setup as a whole is not guaranteed bounded.
Input priority and background work
After shutdown/stop checks, present pending input feedback and service ready terminal input before background sources. Keyboard, paste, and mouse feedback bypass streaming frame pacing; accepted asynchronous clipboard results end the completion batch and request an urgent frame. Stale clipboard results remain ignored, and deferred submission retains its ordering boundary.
Forward and apply updates in FIFO batches capped at 64 records and a 2 ms elapsed-time budget checked between records. Preserve image-worker ordering, queue backpressure, session-generation checks, and per-update voice/config observation. Background sources rotate, streaming frames are paced from draw start at a 16 ms interval, and forwarding alone does not request a draw. Continuous input may defer streaming; individual synchronous handlers and renders remain non-preemptible.
Markdown parsing
Use monotonic lookahead and per-parse delimiter metadata for inline markers, links, and images, including incomplete streaming syntax. Preserve the distinct escaping, title, and fence rules instead of repeatedly searching each remaining suffix.
Transcript layout and interactions
Retain wrapped rows for complete line-local Markdown prefixes, refresh content before selecting nearby animated cards, and avoid rebuilding a card twice in one frame. Stop prefix-offset propagation when offsets converge; avoid unnecessary prior-text copies and newline scans on append paths. Invalidate affected code-copy targets immediately when source text changes so paced rendering cannot apply stale byte ranges to replacement text.
Prefix reuse remains conservative around fences, tables, bracket-based links/images, and unfinished lines. Those suffixes and width changes still use full layout; visible animated cards still rebuild their bodies.
Reproducible terminal-output probe
Add an opt-in real-TUI PTY probe with a process-boundary ACP fixture, binary hashes, per-key timing, output bytes, and synchronized-frame counts. Its endpoint is key injection to rendered glyph bytes plus frame-end arrival at the PTY master, not terminal-emulator paint.