Documentation Index
Fetch the complete documentation index at: https://docs.monolex.ai/llms.txt
Use this file to discover all available pages before exploring further.
Actor Over Mutex
For terminal state management, the Actor pattern eliminates the race conditions that Mutex can only mitigate.The Problem: Concurrent State Access
╔═════════════════════════════════════════════════════════════════╗
║ THE TERMINAL CONCURRENCY PROBLEM ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Multiple Sources Want Terminal State Simultaneously: ║
║ ║
║ PTY Output Thread ──────┬ ║
║ │ ║
║ User Input Handler ─────┼────────▶ Terminal State ║
║ │ (Grid) ║
║ Renderer Timer ─────────┘ ▲ ║
║ │ ║
║ Close Handler ║
║ │ ║
║ Resize Handler ║
║ ║
║ ─────────────────────────────────────────────────────────── ║
║ ║
║ Without Protection: DATA RACE ║
║ ║
║ Thread A: Reading cell[5,10] for render ║
║ Thread B: Writing cell[5,10] from PTY output ║
║ Result: Undefined behavior, corrupted display ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Solution A: Mutex (Traditional)
╔═════════════════════════════════════════════════════════════════╗
║ MUTEX APPROACH ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Concept: "One lock guards the state, everyone waits" ║
║ ║
║ Thread A ───────▶ ┌──────────┐ ║
║ │ MUTEX │ ──────▶ Terminal State ║
║ Thread B ───────▶ │ (lock) │ ║
║ └──────────┘ ║
║ Thread C ───────▶ │ ║
║ │ ║
║ ┌────┴────┐ ║
║ │ WAITING │ Thread B, C blocked ║
║ └─────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Problems with Mutex for Terminal
╔═════════════════════════════════════════════════════════════════╗
║ 1. LOCK CONTENTION ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ PTY output: 1000 writes/sec ║
║ Renderer: 60 reads/sec ║
║ Each competes for same lock ║
║ ║
║ Timeline: ║
║ PTY: [LOCK──write──UNLOCK][LOCK──write──UNLOCK][LOCK...] ║
║ Render: [WAIT.......][LOCK──read──UNLOCK][WAIT...] ║
║ ║
║ Renderer starves or PTY blocks -> bad UX ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ 2. PRIORITY INVERSION ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Low-priority resize holds lock ║
║ High-priority PTY output waits ║
║ Terminal feels sluggish ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ 3. DEADLOCK RISK ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Multiple resources (state + socket + log) ║
║ Lock ordering mistakes -> deadlock ║
║ Debugging nightmare ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Solution B: Actor Pattern (MonoTerm)
╔═════════════════════════════════════════════════════════════════╗
║ ACTOR APPROACH ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Concept: "Single owner processes messages in order" ║
║ ║
║ ┌──────────────────────────────────────┐ ║
║ │ MPSC CHANNEL │ ║
║ │ (Multiple Producer, Single Consumer) │ ║
║ └────────────────┬─────────────────────┘ ║
║ │ ║
║ PTY Thread ────▶ tx.send(PtyData) ─┤ ║
║ │ ║
║ Input Handler ──▶ tx.send(Input) ──┼───▶ rx.recv() ║
║ │ │ ║
║ Resize Handler ─▶ tx.send(Resize) ─┤ ▼ ║
║ │ ┌───────────────┐ ║
║ Close Handler ──▶ tx.send(Close) ──┘ │ SessionActor │ ║
║ │ (SINGLE OWNER)│ ║
║ │ │ ║
║ │ * All State │ ║
║ │ * No locks │ ║
║ │ * Sequential │ ║
║ └───────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Why Actor Wins for Terminal
╔═════════════════════════════════════════════════════════════════╗
║ 1. NO LOCK CONTENTION ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ Messages queue, Actor processes sequentially ║
║ No thread ever blocks another ║
║ ║
║ Timeline: ║
║ PTY: [send][send][send][send][send] ◀── Never blocks ║
║ Actor: [recv──process──][recv──process──][recv──...] ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ 2. GUARANTEED ORDER ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ MPSC channel preserves message order ║
║ State transitions are predictable ║
║ Easy to reason about ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ 3. NO DEADLOCK POSSIBLE ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ No locks = no deadlock ║
║ Single owner = no circular waits ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Side-by-Side Comparison
Scenario: PTY outputs 100 lines while user resizes window╔═════════════════════════════════════════════════════════════════╗
║ MUTEX APPROACH ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ T=0ms PTY: lock() ────────────────────────────────┬ ║
║ T=1ms Resize: lock() -> BLOCKED │ ║
║ T=2ms PTY: write line 1 │ ║
║ T=3ms PTY: write line 2 │ Resize ║
║ ... │ WAITS ║
║ T=50ms PTY: write line 50 │ ║
║ T=51ms PTY: unlock() ──────────────────────────────┘ ║
║ T=52ms Resize: lock() -> SUCCESS, but 50ms late! ║
║ T=53ms Resize: apply new dimensions ║
║ T=54ms Resize: unlock() ║
║ ║
║ Result: Resize delayed, possible visual glitch ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
╔═════════════════════════════════════════════════════════════════╗
║ ACTOR APPROACH ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ T=0ms PTY: tx.send(Line1) ────────────────────┬ ║
║ T=0ms Resize: tx.send(Resize) ────────────────┼─▶ Channel ║
║ T=1ms PTY: tx.send(Line2) ────────────────────┘ ║
║ ... (all sends complete immediately) │ ║
║ │ ║
║ T=0ms Actor: recv(Line1) -> process │ ║
║ T=1ms Actor: recv(Resize) -> apply dimensions │ ║
║ T=2ms Actor: recv(Line2) -> process with NEW dimensions ║
║ ... ║
║ ║
║ Result: Resize processed in order, no blocking, correct dims ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
Comparison Table
| Aspect | Mutex | Actor |
|---|---|---|
| Blocking | Writers block readers | No blocking (async send) |
| Order | Race for lock | FIFO guaranteed |
| Deadlock | Possible | Impossible |
| Complexity | Lock ordering rules | Message types only |
| Debug | Hard (race conditions) | Easy (sequential log) |
| Resize UX | May delay or glitch | Smooth, in-order |
Pattern History
The Actor pattern is not Rust-specific. It has a 50+ year track record.╔═════════════════════════════════════════════════════════════════╗
║ ACTOR PATTERN TIMELINE ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ 1973: Carl Hewitt proposes Actor Model ║
║ │ ║
║ ▼ ║
║ 1986: Erlang/OTP implements Actor for telecom ║
║ (99.9999999% uptime) ║
║ │ ║
║ ▼ ║
║ 2009: Scala Akka brings Actor to JVM ║
║ │ ║
║ ▼ ║
║ 2015: Go channels (CSP, similar concept) ║
║ │ ║
║ ▼ ║
║ 2020: Rust Tokio async channels ║
║ │ ║
║ ▼ ║
║ 2024: MonoTerm SessionActor ║
║ ║
║ The pattern is LANGUAGE-INDEPENDENT. ║
║ Rust just makes it easy with ownership + MPSC channels. ║
║ ║
╚═════════════════════════════════════════════════════════════════╝
SessionActor in MonoTerm
The SessionActor owns all terminal state. No locks needed.╔══════════════════════════════════════════════════════════════════╗
║ SESSIONACTOR STRUCTURE ║
╠══════════════════════════════════════════════════════════════════╣
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ SessionActor │ ║
║ │ │ ║
║ │ sessions: HashMap<String, SessionState> ← Owns state │ ║
║ │ grid_workers: HashMap<String, Sender> ← Per-session │ ║
║ │ rx: Receiver<SessionCommand> ← Single consumer│ ║
║ │ tx: Sender<SessionCommand> ← Cloned to all │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ SessionCommand (Message Types) │ ║
║ │ │ ║
║ │ * CreateSession { cols, rows } │ ║
║ │ * HandlePtyData { session_id, data } │ ║
║ │ * ResizeSession { session_id, cols, rows } │ ║
║ │ * CloseSession { session_id } │ ║
║ │ * ... │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌───────────────────────────────────────────────────────────┐ ║
║ │ The Actor Loop │ ║
║ │ │ ║
║ │ loop { │ ║
║ │ match rx.recv().await { │ ║
║ │ HandlePtyData { id, data } => { │ ║
║ │ // No lock - we OWN the state │ ║
║ │ sessions[id].process(data) │ ║
║ │ } │ ║
║ │ ResizeSession { id, cols, rows } => { │ ║
║ │ // No lock - sequential processing │ ║
║ │ sessions[id].resize(cols, rows) │ ║
║ │ } │ ║
║ │ } │ ║
║ │ } │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────┘ ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
Summary
╔═════════════════════════════════════════════════════════════════╗
║ ACTOR OVER MUTEX: KEY TAKEAWAYS ║
╠═════════════════════════════════════════════════════════════════╣
║ ║
║ 1. Mutex "works" but creates contention under high output ║
║ 2. Actor eliminates contention by design (single owner) ║
║ 3. FIFO ordering means predictable state transitions ║
║ 4. No locks = no deadlock = no debugging nightmares ║
║ 5. Pattern is 50+ years old, battle-tested in Erlang telecom ║
║ ║
║ ─────────────────────────────────────────────────────────── ║
║ ║
║ For terminal state: ACTOR > MUTEX ║
║ ║
║ But Actor alone does not solve high-output rendering... ║
║ -> See: Actor + ACK Synergy ║
║ ║
╚═════════════════════════════════════════════════════════════════╝