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.
ACK Flow Control
ACK Gate is MonoTerm’s solution for preventing IPC flooding when fast producers meet slow consumers.The Problem: IPC Flooding
Without flow control, fast producers can overwhelm slow consumers.╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ THE FLOODING PROBLEM ║
║ ║
║ ║
║ Scenario: Running "cat large_file.txt" (1GB file) ║
║ ║
║ ║
║ ┌──────────────────┐ ┌──────────────────┐ ║
║ │ │ │ │ ║
║ │ Rust Backend │ │ TypeScript UI │ ║
║ │ │ │ │ ║
║ │ Speed: │ │ Speed: │ ║
║ │ 500M chars/sec │ │ 60 FPS render │ ║
║ │ │ │ │ ║
║ │ ════════════▶ │ │ ════▶ │ ║
║ │ VERY FAST │ │ SLOWER │ ║
║ │ │ │ │ ║
║ └──────────────────┘ └──────────────────┘ ║
║ ║
║ ║
║ Without Flow Control: ║
║ ║
║ Rust TypeScript ║
║ │ │ ║
║ │ GridUpdate #1 ═══════════════════▶ │ Processing... ║
║ │ GridUpdate #2 ═══════════════════▶ │ │ ║
║ │ GridUpdate #3 ═══════════════════▶ │ │ Queue ║
║ │ GridUpdate #4 ═══════════════════▶ │ │ growing ║
║ │ GridUpdate #5 ═══════════════════▶ │ │ rapidly ║
║ │ GridUpdate #6 ═══════════════════▶ │ ▼ ║
║ │ ... │ ║
║ │ │ MEMORY ║
║ │ │ EXHAUSTED ║
║ ▼ ▼ ║
║ ║
║ ║
║ Result: ║
║ - Event queue grows unbounded ║
║ - Memory usage spikes ║
║ - UI becomes unresponsive ║
║ - Application may crash ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
The Solution: ACK Handshake
ACK Gate implements a simple handshake: send one, wait for acknowledgment.╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ ACK GATE FLOW CONTROL ║
║ ║
║ ║
║ Principle: ║
║ ║
║ "Do not send the next update until the frontend ║
║ confirms it has processed the previous one." ║
║ ║
║ ║
║ ┌──────────────────┐ ┌──────────────────┐ ║
║ │ │ │ │ ║
║ │ Rust Backend │ │ TypeScript UI │ ║
║ │ │ │ (Injector) │ ║
║ │ │ │ │ ║
║ └────────┬─────────┘ └────────┬─────────┘ ║
║ │ │ ║
║ │ 1. GridUpdate │ ║
║ │ ═══════════════════════════▶ ║
║ │ │ ║
║ │ [WAITING] │ 2. Process update ║
║ │ │ 3. Inject to display ║
║ │ 4. ACK │ 4. Send ACK ║
║ │ ◀═══════════════════════════ ║
║ │ │ ║
║ │ 5. GridUpdate │ ║
║ │ ═══════════════════════════▶ ║
║ │ │ ║
║ │ [WAITING] │ 6. Process... ║
║ │ │ ║
║ │ 7. ACK │ ║
║ │ ◀═══════════════════════════ ║
║ │ │ ║
║ ▼ ▼ ║
║ ║
║ ║
║ Key Insight: ║
║ The producer (Rust) is throttled by the consumer (TS). ║
║ Queue size is always 0 or 1. Never grows unbounded. ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
State Machine
The ACK Gate maintains simple state to control the flow.╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ ACK GATE STATE MACHINE ║
║ ║
║ ║
║ ┌───────────────────────────────────────────┐ ║
║ │ │ ║
║ │ IDLE STATE │ ║
║ │ waiting_ack = false │ ║
║ │ pending_data = false │ ║
║ │ │ ║
║ └─────────────────┬─────────────────────────┘ ║
║ │ ║
║ │ PTY data arrives ║
║ ▼ ║
║ ┌───────────────────────────────────────────┐ ║
║ │ │ ║
║ │ Process data with VTE parser │ ║
║ │ Emit GridUpdate to frontend │ ║
║ │ Set waiting_ack = true │ ║
║ │ │ ║
║ └─────────────────┬─────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌───────────────────────────────────────────┐ ║
║ │ │ ║
║ │ WAITING STATE │ ║
║ │ waiting_ack = true │ ║
║ │ │ ║
║ └───────────┬─────────────────┬─────────────┘ ║
║ │ │ ║
║ More PTY data │ │ ACK received ║
║ arrives │ │ ║
║ ▼ ▼ ║
║ ┌────────────────────┐ ┌────────────────────┐ ║
║ │ │ │ │ ║
║ │ Process data │ │ pending_data │ ║
║ │ (update state) │ │ = true? │ ║
║ │ │ │ │ ║
║ │ Set pending = true │ └─────────┬──────────┘ ║
║ │ │ │ ║
║ │ DO NOT EMIT │ YES │ NO ║
║ │ (wait for ACK) │ │ ║
║ │ │ ▼ ║
║ └────────────────────┘ ┌────────────────────┐ ║
║ │ │ ║
║ │ Emit fresh update │ ║
║ │ Set waiting = true │ ║
║ │ Set pending = false│ ║
║ │ │ ║
║ └────────────────────┘ ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
Why Full Update on ACK?
When ACK arrives and there’s pending data, we send the complete current state.╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ THE PENDING DATA PROBLEM ║
║ ║
║ ║
║ Scenario: ║
║ While waiting for ACK, 5 more PTY chunks arrived. ║
║ Each chunk modified the grid state. ║
║ ║
║ ║
║ Time Grid State What Happened ║
║ ──── ─────────── ────────────── ║
║ T1 "Hello" Sent GridUpdate, waiting for ACK ║
║ T2 "Hello W" Chunk 1 arrived, processed ║
║ T3 "Hello Wo" Chunk 2 arrived, processed ║
║ T4 "Hello Wor" Chunk 3 arrived, processed ║
║ T5 "Hello Worl" Chunk 4 arrived, processed ║
║ T6 "Hello World" Chunk 5 arrived, processed ║
║ T7 ─── ACK received for T1 ║
║ ║
║ ║
║ Wrong Approach: ║
║ ║
║ Send incremental updates for T2, T3, T4, T5, T6? ║
║ ──▶ 5 more round trips ║
║ ──▶ Complex merging logic ║
║ ──▶ Risk of ordering issues ║
║ ║
║ ║
║ Correct Approach (ACK Gate): ║
║ ║
║ Request FULL update at T7. ║
║ Send current complete state: "Hello World" ║
║ ──▶ One round trip ║
║ ──▶ Simple: just send current state ║
║ ──▶ No partial update complexity ║
║ ║
║ ║
║ ┌─────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ "When in doubt, send the complete state. │ ║
║ │ It's simpler and always correct." │ ║
║ │ │ ║
║ └─────────────────────────────────────────────┘ ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
Timeout Fallback
What if the frontend crashes or ACK is lost?╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ ACK TIMEOUT MECHANISM ║
║ ║
║ ║
║ Problem: ║
║ ║
║ If ACK never arrives, the terminal would freeze forever. ║
║ ║
║ ║
║ Solution: 1-second timeout ║
║ ║
║ ║
║ Rust TypeScript ║
║ │ │ ║
║ │ GridUpdate ══════════════════════▶ │ ║
║ │ │ ║
║ │ Start timer: 1 second │ Crashed ║
║ │ │ │ or ║
║ │ │ │ ACK lost ║
║ │ │ │ ║
║ │ │ 100ms... │ ║
║ │ │ 200ms... │ ║
║ │ │ ... │ ║
║ │ │ 1000ms │ ║
║ │ ▼ │ ║
║ │ TIMEOUT! │ ║
║ │ │ ║
║ │ - Set waiting_ack = false │ ║
║ │ - Resume sending updates │ ║
║ │ - Log warning for debugging │ ║
║ │ │ ║
║ │ GridUpdate ══════════════════════▶ │ ║
║ │ (Terminal recovered) │ ║
║ ▼ ▼ ║
║ ║
║ ║
║ Why 1 Second? ║
║ ║
║ - Long enough: Normal ACK takes < 100ms ║
║ - Short enough: User notices frozen terminal quickly ║
║ - 1 sec allows frontend to recover from temporary issues ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
Two Layers of Flow Control
ACK Gate works alongside BSU/ESU for complementary control.╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ TWO LAYERS OF FLOW CONTROL ║
║ ║
║ ║
║ Layer 1: ACK Gate (IPC Backpressure) ║
║ ┌─────────────────────────────────────────────────────┐ ║
║ │ Rust Backend ◀──────▶ TypeScript Frontend │ ║
║ │ │ ║
║ │ - 1 second timeout │ ║
║ │ - Prevents event queue overflow │ ║
║ │ - Process-level backpressure │ ║
║ └─────────────────────────────────────────────────────┘ ║
║ ║
║ Layer 2: BSU/ESU (Frame Synchronization) ║
║ ┌─────────────────────────────────────────────────────┐ ║
║ │ Application ◀──────▶ Terminal Renderer │ ║
║ │ │ ║
║ │ - 16ms timeout │ ║
║ │ - Prevents screen tearing │ ║
║ │ - Atomic frame rendering │ ║
║ └─────────────────────────────────────────────────────┘ ║
║ ║
║ ║
║ WHY TWO TIMEOUTS? ║
║ ║
║ BSU/ESU (16ms): ║
║ - Application-level frame control ║
║ - Fast timeout for responsive UI ║
║ - Works for well-behaved TUI apps ║
║ ║
║ ACK Gate (1000ms): ║
║ - Process-level IPC control ║
║ - Longer timeout for system delays ║
║ - Prevents memory exhaustion ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝
Comparison with Other Terminals
How other terminals handle (or don’t handle) flow control.╔═════════════════════════════════════════════════════════════════════════╗
║ ║
║ FLOW CONTROL COMPARISON ║
║ ║
║ ║
║ ┌─────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ MONOTERM (ACK Gate) │ ║
║ │ │ ║
║ │ Producer ──▶ [ACK Handshake] ──▶ Consumer │ ║
║ │ │ ║
║ │ Explicit flow control │ ║
║ │ No memory growth │ ║
║ │ Consumer sets the pace │ ║
║ │ One round-trip latency │ ║
║ │ │ ║
║ └─────────────────────────────────────────────────────┘ ║
║ ║
║ ┌─────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ NATIVE TERMINALS (Alacritty) │ ║
║ │ │ ║
║ │ Producer ──▶ [Mutex Lock] ──▶ Consumer │ ║
║ │ │ ║
║ │ Same process, simple │ ║
║ │ No IPC overhead │ ║
║ │ Lock contention possible │ ║
║ │ Not applicable to IPC scenarios │ ║
║ │ │ ║
║ └─────────────────────────────────────────────────────┘ ║
║ ║
║ ┌─────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ WEB TERMINALS (xterm.js default) │ ║
║ │ │ ║
║ │ Producer ──────────────────────▶ Consumer │ ║
║ │ │ ║
║ │ Simplest implementation │ ║
║ │ Lowest latency │ ║
║ │ Queue can grow unbounded │ ║
║ │ UI can freeze under load │ ║
║ │ │ ║
║ └─────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════════════╝
Summary
╔════════════════════════════════════════════════════════════════════════╗
║ ║
║ ACK GATE IN ONE DIAGRAM ║
║ ║
║ ║
║ ┌────────────────────────┐ ║
║ │ │ ║
║ │ RUST BACKEND │ ║
║ │ │ ║
║ │ ┌────────────────┐ │ ║
║ │ │ │ │ ║
║ │ │ PTY Data In │ │ ║
║ │ │ │ │ ║
║ │ └───────┬────────┘ │ ║
║ │ │ │ ║
║ │ v │ ║
║ │ ┌────────────────┐ │ ║
║ │ │ │ │ ║
║ │ │ VTE Process │ │ ║
║ │ │ │ │ ║
║ │ └───────┬────────┘ │ ║
║ │ │ │ ║
║ │ v │ ║
║ │ ┌────────────────┐ │ ║
║ │ │ waiting_ack │ YES ║
║ │ │ == true? │───────▶[SKIP EMIT] ║
║ │ │ │ (set pending) ║
║ │ └───────┬────────┘ │ ║
║ │ │ NO │ ║
║ │ v │ ║
║ │ ┌────────────────┐ │ ║
║ │ │ │ │ ║
║ │ │ EMIT UPDATE │─────────────────────────▶ ║
║ │ │ waiting=true │ │ ║
║ │ │ │ │ ║
║ │ └────────────────┘ │ ║
║ │ │ ║
║ └────────────────────────┘ ║
║ │ ║
║ │ GridUpdate ║
║ │ ║
║ v ║
║ ┌────────────────────────┐ ║
║ │ │ ║
║ │ TYPESCRIPT FRONTEND │ ║
║ │ │ ║
║ │ 1. Receive GridUpdate ║
║ │ 2. Inject into xterm.js ║
║ │ 3. Trigger WebGL render ║
║ │ 4. Send ACK ═══════════════════▶ ║
║ │ │ ║
║ └────────────────────────┘ ║
║ ║
╚════════════════════════════════════════════════════════════════════════╝