Skip to main content

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

AspectMutexActor
BlockingWriters block readersNo blocking (async send)
OrderRace for lockFIFO guaranteed
DeadlockPossibleImpossible
ComplexityLock ordering rulesMessage types only
DebugHard (race conditions)Easy (sequential log)
Resize UXMay delay or glitchSmooth, 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                                    ║
║                                                                 ║
╚═════════════════════════════════════════════════════════════════╝