Skip to content

fix(relay): drop unresponsive browser sockets so they stop holding stream slots - #709

Merged
alari76 merged 1 commit into
mainfrom
fix/relay-stream-leak
Oct 4, 2026
Merged

alari76 merged 1 commit into
mainfrom
fix/relay-stream-leak

Conversation

@alari76

@alari76 alari76 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Problem

On codekin.ai, sessions stopped loading while the session list (REST) kept working. The connector stopped receiving stream_open after ~19:36 yesterday, while the relay audit log still showed session_viewed attempts, so the relay was refusing streams before forwarding them.

Cause: BrowserHub has no liveness check on browser sockets, and nginx keeps /relay/ sockets for up to 24h (proxy_read_timeout 86400). A tab that vanishes without a close handshake keeps its socket — and up to MAX_CHANNELS_PER_CLIENT (4) channels — counted against MAX_CHANNELS_PER_MACHINE (32). After about eight such tabs, every new stream gets tooManyChannels. Restarting the connector cleared it, which matches this explanation (a reconnect runs failPending and drops all channels).

Changes

  • Heartbeat: BrowserHub sends a WebSocket ping to every client every 30s and terminates any socket that missed the previous ping. Termination runs the existing close path, which releases its channels on the machine. Browsers answer protocol pings themselves, so the frontend doesn't change.
  • Logging: the relay now logs refused stream opens with the error code (it was silent before). The connector now also logs relay-initiated channel closes, so its open and close lines balance.

Tests

  • New: a browser with autoPong: false gets dropped and its local session socket closes. This test fails without the fix.
  • New: a responsive browser keeps its channel across several heartbeat intervals.
  • server/relay: 415/415 pass; tsc --noEmit is clean.

🤖 Generated with Claude Code

…ream slots

A browser tab that disappears without a close handshake (laptop asleep,
phone off wifi) kept its relay socket open until nginx's 24h read timeout,
along with up to four session channels each. Enough of these filled the
machine's 32-channel budget and every new session stream was refused, so
sessions stopped loading on codekin.ai while REST kept working.

- BrowserHub pings every browser socket every 30s and terminates those
  that did not answer the previous ping; the normal close path releases
  their channels. Browsers answer protocol pings natively.
- Log refused stream opens on the relay with the error code, and log
  relay-initiated channel closes on the connector, so the open/close
  lines in the connector log balance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alari76
alari76 merged commit 83dee92 into main Oct 4, 2026
2 checks passed
@alari76
alari76 deleted the fix/relay-stream-leak branch October 4, 2026 05:55
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.

1 participant