Skip to content

Drain PTY output on a dedicated reader thread - #366

Merged
forketyfork merged 4 commits into
mainfrom
feat/pty-reader-thread
Aug 21, 2026
Merged

Drain PTY output on a dedicated reader thread#366
forketyfork merged 4 commits into
mainfrom
feat/pty-reader-thread

Conversation

@forketyfork

Copy link
Copy Markdown
Owner

Issue

Architect drained each session's PTY only once per frame. Chatty JLine-based TUIs could therefore fill the kernel PTY buffer, block their writes, and leave DEC-2026 synchronized-output windows open across render frames, causing stale output and input latency.

Solution

Add a dedicated reader thread that polls all registered PTY masters and drains readable data into per-session mutex-guarded ring buffers. The main thread consumes those buffers for VT parsing and terminal mutation, preserving single-threaded terminal state while decoupling ingestion from render pacing.

Session registration and retirement use a mutex handshake so PTY fds and buffers cannot be accessed after teardown. Tests cover ring ordering, wraparound, full-buffer recovery, EOF/error handling, wake deduplication, retirement, and chunked DEC-2026 output.

Context

The implementation follows the committed PTY reader thread plan and documents the thread model and JLine performance investigation in the architecture and performance-debugging guides.

Issue: PTY output was drained only once per frame, allowing chatty JLine TUIs to backpressure on the kernel buffer and leave DEC-2026 windows stale.

Solution: Poll registered PTY masters on a dedicated reader thread and drain each into a per-session mutex-guarded ring buffer. Keep VT parsing and terminal mutation on the main thread, use a retire handshake for safe fd and buffer teardown, and cover the buffer, wake, retirement, and chunked DEC-2026 paths with tests.
@forketyfork
forketyfork marked this pull request as ready for review August 21, 2026 11:03
@forketyfork
forketyfork requested a lite review from Copilot August 21, 2026 11:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR decouples PTY ingestion from render pacing by introducing a dedicated background PTY reader thread that drains readable master fds into per-session ring buffers, while keeping VT parsing and terminal-state mutation on the main thread. This addresses output stalls and input latency caused by frame-bounded PTY reads with chatty TUIs (notably DEC-2026 synchronized-output workloads).

Changes:

  • Added PtyReader + PtyOutputBuffer (mutex-guarded SPSC byte ring) and a polling reader thread that drains PTYs and posts wake events with deduplication.
  • Updated SessionState to create/register/retire per-session buffers and to consume output from the ring buffer in processOutput instead of reading the PTY directly (plus new regression test).
  • Replaced the prior pty_watcher wiring in the runtime with the new reader thread and updated docs describing the thread model and the JLine investigation.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/session/state.zig Registers each spawned session with the PTY reader, retires on teardown, and consumes output from the ring buffer in processOutput.
src/session/pty_watcher.zig Deleted legacy “readability watcher” thread implementation (superseded by reader thread).
src/session/pty_reader.zig New module implementing the PTY polling/drain thread and per-session output ring buffer + tests.
src/app/runtime.zig Starts/stops the PTY reader thread, passes it into sessions, and removes per-frame fd list refresh.
src/main.zig Updates test registry to include pty_reader.zig tests instead of pty_watcher.zig.
src/app/layout.zig Updates SessionState.init callsites to include the new reader parameter (null in tests).
docs/superpowers/plans/2026-08-21-pty-reader-thread.md Adds the implementation plan used to structure the change set.
docs/perf-debugging.md Documents the JLine/DEC-2026 small-write producer behavior and how the reader thread resolves it.
docs/ARCHITECTURE.md Updates architectural description/diagrams to reflect the new PTY reader + ring-buffer model.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/session/pty_reader.zig Outdated
Issue: Review identified that a zero poll snapshot caused by full output buffers used the 100ms idle delay, adding avoidable backpressure after the main thread consumed data.

Solution: Classify buffer snapshots as ready, full, or closed. Retry full buffers with a bounded 2ms backoff while retaining the longer idle delay for empty or closed registries, and test the distinction.
Issue: Branch review identified that pumpFd could append bytes and then report full or closed, preventing the reader from posting the wake needed by the main thread. It also identified an unused Shell.read API after PTY reads moved to the reader thread.

Solution: Report progressed whenever pumpFd appended bytes, including terminal full, EOF, and read-error exits, and update the corresponding tests. Remove the dead Shell.read method while retaining the existing direct posix read path.
@forketyfork
forketyfork merged commit 6d58418 into main Aug 21, 2026
4 checks passed
@forketyfork
forketyfork deleted the feat/pty-reader-thread branch August 21, 2026 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants