Recover from a btcdata restart without reloading the page
pull-request / validate (pull_request) Successful in 24s
pull-request / validate (pull_request) Successful in 24s
htmx's WebSocket extension reconnects only after close codes 1006/1011/1012/1013. _ws_proxy closed the browser socket with a normal 1000 on every upstream exit, so htmx read a btcdata restart as a deliberate shutdown and left both panels frozen until the visitor reloaded. New browser connections always worked, which hid the defect from non-browser checks. The proxy now reconnects upstream behind the still-open browser socket on a bounded schedule, resets that schedule after a connection that lasted long enough to count as healthy, and closes with 1012 only once the schedule runs out, so recovery falls back to htmx's own backoff. A concurrent reader detects a departed browser, which a dead upstream would otherwise be indistinguishable from, and an unrenderable payload is logged and skipped rather than ending the stream. tests/test_ws_proxy.py covers this against a real loopback WebSocket server: same socket recovery across an upstream restart for both streams, the retryable close code when the upstream stays down, and skip-on-unrenderable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,27 @@ and `/api/strike/ws`, converts each JSON message to an HTML fragment (`price_jso
|
||||
`strike_json_to_html`) and forwards it. `BTCDATA_URL` is the single point of contact;
|
||||
`_btcdata_ws_url` rewrites its scheme to `ws`/`wss`.
|
||||
|
||||
### Recovering from a btcdata restart
|
||||
|
||||
htmx's WebSocket extension reconnects only after an abnormal close
|
||||
(1006/1011/1012/1013); a normal 1000 close means "the server is done" and the panel stays
|
||||
frozen until the visitor reloads. So a btcdata restart must never reach the browser as a
|
||||
normal close:
|
||||
|
||||
- `_ws_proxy` reconnects upstream behind the still-open browser socket, on the bounded
|
||||
`UPSTREAM_RETRY_DELAYS_SECONDS` schedule. An ordinary restart is invisible to the page —
|
||||
the last values stay on screen and updates resume.
|
||||
- A connection that lasted `UPSTREAM_STABLE_SECONDS` resets that schedule, so a long-lived
|
||||
page treats a later restart as a fresh outage rather than a continuing one.
|
||||
- When the schedule runs out, the proxy closes with `BROWSER_RETRY_CLOSE_CODE` (1012) and
|
||||
htmx's own backoff takes over. Every server-side exit uses that code; only a browser that
|
||||
has already left can see a plain close.
|
||||
- A payload that cannot be rendered is logged and skipped, not treated as a dead stream.
|
||||
|
||||
`tests/test_ws_proxy.py` covers this against a real loopback WebSocket server, including
|
||||
same-socket recovery across an upstream restart and the retryable close code. The regression
|
||||
to watch for is a 1000 close after upstream loss.
|
||||
|
||||
## Source Layout
|
||||
|
||||
btcprice acts as a WebSocket proxy between the browser and btcdata:
|
||||
|
||||
Reference in New Issue
Block a user