Skip to main content

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 ═══════════════════▶                               ║
║    │                        │                                          ║
║    └────────────────────────┘                                          ║
║                                                                        ║
╚═══════════════════════════════════════════════════════════════════════╝