Skip to content

fix: bounded frame channel to preserve fast typing#225

Merged
psmux merged 2 commits into
psmux:masterfrom
tarikguney:fix/bounded-frame-channel
Apr 16, 2026
Merged

fix: bounded frame channel to preserve fast typing#225
psmux merged 2 commits into
psmux:masterfrom
tarikguney:fix/bounded-frame-channel

Conversation

@tarikguney

@tarikguney tarikguney commented Apr 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Replaces the single-slot Arc<Mutex<Option<String>>> frame push (from 694156e) with a bounded sync_channel (capacity 16)
  • Fixes fast typing regression where cursor advances but characters are not rendered because intermediate frames were overwritten before the client consumed them
  • Memory remains bounded to O(FRAME_CHANNEL_CAPACITY * clients) — still prevents the original unbounded growth during rapid copy-mode scrolling

What changed

src/types.rs: FrameSlotFrameChannel backed by mpsc::sync_channel(16). push_frame() uses try_send() — if the channel is full (sustained scroll burst), the frame is skipped rather than blocking, so the server is never stalled. Dead channels are pruned via Disconnected.

src/server/connection.rs: Writer thread now locks the channel receiver once and drains all queued frames per loop iteration via try_recv(), delivering batched frames without the 5ms polling delay between them.

Why this approach

Approach Memory bound Fast typing Scroll burst
Original unbounded channel ❌ O(frames) ✅ all frames queued ❌ 1 GB leak
Single-slot (694156e) ✅ O(1) per client ❌ drops intermediate frames ✅ bounded
Bounded channel (this PR) ✅ O(16) per client ✅ 16-frame burst buffer ✅ graceful skip when full

Fixes #224

Test plan

  • Type quickly in a psmux pane — all characters should render without gaps
  • Run test_scroll_memory.ps1 — memory should stay bounded (regression test from 694156e)
  • Attach multiple clients and type fast in each — no cross-client interference

Tarik Guney and others added 2 commits April 15, 2026 13:24
…fast typing

Commit 694156e replaced the unbounded mpsc::channel per client with a
single-slot Arc<Mutex<Option<String>>> to fix a memory leak during rapid
copy-mode scrolling.  The single-slot design overwrites any unconsumed
frame, which drops intermediate frames during fast typing — the cursor
advances but characters are not rendered (psmux#224).

Replace the single-slot with a bounded sync_channel (capacity 16).
This preserves intermediate frames during normal typing bursts while
still bounding memory to O(FRAME_CHANNEL_CAPACITY * clients) instead
of the original unbounded O(frames * clients).

When the channel is full under sustained high-throughput (e.g. rapid
scroll), the frame is skipped rather than blocking, so the server is
never stalled.  The client already has 16 frames queued to drain, so
it won't miss meaningful content.

The writer thread now drains all queued frames per loop iteration
via try_recv(), ensuring batched delivery without the 5ms polling
delay between frames.

Fixes psmux#224

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When remain-on-exit is on, prune_exited() marks panes as dead but keeps
them in the tree.  The hook-firing logic only checked whether the tree
leaf count decreased (any_pruned), which never happens in the
remain-on-exit case since dead panes are still counted as leaves.

Fix: prune_exited() now returns a newly_dead_count tracking panes that
transitioned alive→dead in each call.  reap_children() propagates this
as a separate any_newly_dead flag alongside any_pruned.  The server
event loop fires pane-died/pane-exited hooks when either flag is true,
but only resizes the layout when panes were actually removed.

This matches tmux semantics: hooks fire when the child process exits,
regardless of whether the pane is retained or removed.  The dead flag
on the pane prevents hooks from firing repeatedly on subsequent reap
ticks — has_any_exited() skips already-dead panes.

Fixes psmux#227

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@psmux psmux merged commit c8f099a into psmux:master Apr 16, 2026
3 checks passed
@psmux

psmux commented Apr 16, 2026

Copy link
Copy Markdown
Owner

Hey @tarikguney, this is merged now. Really appreciate you taking the time to not only identify the root cause but also implement the fix yourself.

Your analysis of the single slot overwrite behavior was spot on. The bounded sync_channel(16) approach is exactly the right tradeoff: it preserves intermediate frames during normal typing while still capping memory for the pathological scroll bursts that the original fix was targeting. Clean, well documented code too.

I also noticed you included the pane died/pane exited hooks fix for remain on exit in the second commit, which is a nice bonus. Thanks again for the solid contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fast typing drops intermediate frames — characters not rendered while cursor advances

2 participants