Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Threads, Async I/O, Concurrency, and Backpressure

Zellij’s concurrency follows state ownership.

Dedicated owner threads

One session starts threads for Screen, PTY commands, Wasm plugins, PTY writes, and background jobs. Each blocks on typed channel instructions and mutates its own state serially. Pane layout changes therefore do not require many fine- grained locks inside Screen.

Async work where readiness matters

Spawning a terminal returns an AsyncReader. A Tokio task per terminal awaits PTY readability using the platform implementation (AsyncFd on Unix). It sends ScreenInstruction::PtyBytes into a bounded Screen channel of capacity 50. Async I/O lets a few runtime threads wait on many child outputs.

The shared Tokio runtime also supplies timers, HTTP/download work, and oneshot completion waiting. It is not the owner of tabs or pane geometry.

A deliberate sync/async bridge

The internal instruction channels are synchronous. A PTY-reader task crosses that boundary with spawn_blocking when it sends to Screen. If the bounded channel fills, pressure reaches the blocking pool rather than stalling a Tokio worker directly.

Independent write fairness

PTY input uses a separate writer thread because reading and writing the same terminal in one execution path can deadlock some programs. It keeps a VecDeque per terminal, writes until the kernel would block, then tries the next terminal. Each pane’s pending bytes are capped at 10 MiB.

Render coalescing

Every PTY chunk updates pane state, but visible rendering is debounced through the background-jobs owner. Many bursts within roughly 10 ms become one RenderToClients. Screen renders only dirty state and serializes ANSI output per client.

The complete feedback loop is:

child readiness → bounded Screen admission → terminal state mutation
       → debounced render → client output → user input → fair PTY writer

Threads isolate mutable domains. Async tasks multiplex readiness. Bounds and debouncing prevent either mechanism from creating unlimited work.