-
Notifications
You must be signed in to change notification settings - Fork 16
Open
Description
Problem
The tabs.sync.push WebSocket message sends payloads as large as 424KB in a single frame. This causes WebSocket backpressure issues, leading to connection closures and degraded user experience, especially when combined with event loop blocking from session indexing.
Evidence from Production Logs
{"event":"ws_backpressure_close"} // Connection killed due to slow processing
{"event":"terminal_stream_replay_miss"} // 400+ events
{"event":"terminal_stream_gap"} // Data loss from connection issues
Root Cause Analysis
Large Payload Sources
- Tab state synchronization -
tabs.sync.pushsends the complete tab/pane state tree - Session data - Full session metadata for all open terminals
- Terminal scrollback - Large terminal buffers being synced
Why 424KB is Problematic
- WebSocket frame size - Large frames require multiple TCP packets
- Backpressure buildup - If client can't process fast enough, server buffers grow
- Event loop blocking - While processing large payloads, other operations queue up
- Memory pressure - Both client and server must hold full payload in memory
Cascading Effects
When combined with session indexer blocking the event loop for 6+ seconds:
- Server is blocked, can't drain WebSocket send buffers
- Backpressure builds, server closes connection
- Client reconnects, triggering full state resync
- Large payload sent again, cycle repeats
Observed Behavior
ws_backpressure_closeevents in logs- 400+
terminal_stream_replay_miss/terminal_stream_gapevents - Terminal input lag up to 2.5 seconds
- UI freezing during sync operations
Proposed Solutions
Short-term
- Chunk large payloads - Split payloads >64KB into multiple messages
- Compression - Enable permessage-deflate WebSocket compression
- Delta sync - Only send changed portions of state, not full tree
- Lazy loading - Don't include full scrollback in sync, fetch on demand
Long-term
- Binary protocol - Use MessagePack or Protocol Buffers for efficient encoding
- Streaming state - Stream state updates incrementally
- Client-driven sync - Client requests specific data rather than push-all
- Backpressure handling - Implement proper flow control with pause/resume
Implementation Ideas
Chunked Messages
interface ChunkedMessage {
type: 'chunked.start' | 'chunked.data' | 'chunked.end'
messageId: string
chunkIndex: number
totalChunks: number
data?: string // base64 for binary safety
}Delta Sync
interface TabSyncDelta {
type: 'tabs.sync.delta'
added: Tab[]
removed: string[] // tab IDs
updated: { id: string; changes: Partial<Tab> }[]
}Impact
- Medium - Causes intermittent disconnections and data loss
- Exacerbated by session indexer blocking
- Affects users with many tabs/panes or large terminal buffers
Related
- Session indexer blocking - makes backpressure worse
- Memory leaks - large payloads contribute to memory pressure
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels