Repository navigation
Healthy relay connections: heartbeat, per-browser queues, send backpressure, hint for several tabs - #9
Merged
Conversation
…ackpressure
Users who were disconnected could not reconnect until App Service restarted,
and large boards reconnected every few seconds during sync.
- Heartbeat: every 25 s a browser sends a message the server answers. The
server closes a browser silent for six of them (2.5 min), even if the
hosting front end still answers WebSocket pings for it, so connections of
vanished browsers (closed laptops, changed networks) free their slot in the
platform's WebSocket limit instead of piling up until a restart. A browser
that hears nothing for 2.5 min connects again; failed attempts wait longer
(2 s up to 30 s). Browsers without a heartbeat (older versions) are only
closed by pings, as before.
- Per-browser queues: the relay queues messages per browser and a writer sends
them at that browser's pace. Forwarding inside the sender's loop stopped
reading the sender while a slow browser drained; the sender's pong stayed
unread and uvicorn closed the healthy sender ("keepalive ping timeout"), so
the sync of a large board started over and over. A browser more than 64 MiB
behind is disconnected (1013) and fetches the rest when it reconnects.
- Send backpressure: a browser puts the next message on the wire only when its
backlog is below 1 MB (also for reply parts), so its own pong and heartbeat
never wait behind megabytes of upload.
- The image allows 120 s for a WebSocket pong (UVICORN_WS_PING_TIMEOUT).
- A browser that leaves between hello and welcome is removed from the room.
tests/test_relay_flow.py reproduces the closed sender with a stalled browser
(fails on the old relay); test_relay covers heartbeat and silence; README
documents the App Service WebSocket limits per plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t waiting - A tab shows a dismissible hint when the same board is open in other tabs of this browser: every tab loads, stores and syncs the whole board, so a large board open several times makes them all slow. Tabs count each other over the existing identity BroadcastChannel; they count again when a tab opens or closes the board, when a peer leaves (a killed tab never says goodbye) and once a minute. - Send backpressure no longer waits in a hidden tab. Browsers run its timers once a second or once a minute, so a background tab answering a sync request would have sent about 1 MB per minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
These commits reached
fix/websocket-logsafter #8 was merged, so they are not on main yet. Same changes in Enricher: k3mpaxl/Enricher#2. Three problems seen on App Service:What changed
Relay (
cece594)deliver()puts every message for a browser into that browser's own outbox, and one writer task per browser sends at its pace. This covers welcome, actions, frames, sync requests, api-commands and heartbeat replies. A browser more than 64 MiB behind is closed with 1013 and catches up by summary sync when it reconnects.{"type":"heartbeat"}every 25 s and the server answers.helloand then stays silent for six intervals (2.5 min, code 4000), which frees its slot without a restart.drained()makes the browser send the next message or reply part only whenbufferedAmountis below 1 MB.UVICORN_WS_PING_TIMEOUT=120.helloandwelcomeis removed from the room.Tabs (
1fcc0be)pagehide);README. Documents the App Service WebSocket limits per plan (F1: 5, B1: 350) and the cost of several tabs.
Tests
tests/test_relay_flow.pyuses a stalled browser, a fast one and a sender with 40 × 512 KB frames. On the old relay it fails because the sender is closed with 1011.test_relaycovers the heartbeat: it is answered, silence closes with 4000, and browsers without a heartbeat stay connected.query.cjscovers backpressure, including a hidden tab that does not wait.identitye2e test now checks the hint: both tabs show it, it can be dismissed, and it goes away when the other tab closes.pagehide.import queueandattack impactfailed now and then while traces were recorded under load. Without traces they passed 20/20 here and on the base, so the failures look timing-related.🤖 Generated with Claude Code