Threading and I/O
evolvingThreading and I/O
TL;DR. Why phux uses a single-threaded tokio runtime, how
libghostty’s !Send Terminal lives on a LocalSet, and the actor pattern
that lets us spawn one of them per managed PTY without contention. A
multiplexer is I/O-bound; work-stealing buys nothing on the hot path.
One tokio current-thread runtime. A terminal multiplexer is I/O-bound,
not CPU-bound; the work is poll-many-fds-fanout-bytes. A single-threaded
executor is simpler and plenty fast. We pick tokio specifically over mio
or polling because the ecosystem we need (tokio-uds for Unix sockets,
signal-hook-tokio for signals, tokio-util frame codecs) is mature and not
worth reinventing. We do not use the multi-threaded runtime: nothing in the
server’s hot path benefits from work-stealing.
fn main() -> std::io::Result<()> {
tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
.build()?
.block_on(phux_server::run())
}
Hot paths that can go multi-threaded later if needed:
- PTY-byte feed to the canonical
Terminalper pane plus per-client capability rewriting on outboundPANE_OUTPUTframes. Each pane is independent; trivial to fan out viaspawn_blockingor a dedicated worker thread per pane. - Compression of large
PANE_SNAPSHOTbodies before transmission.
We do not parallelize on day one. Premature.