fix(connection): tolerate a slow-but-alive local backend + heap headroom (#21) - #25
Conversation
Under host memory pressure the local ("This device") backend answers
slowly, and the reconnect supervisor abandoned each attempt at a fixed
15s budget and then backed off — repeatedly giving up on a
briefly-slow-but-alive backend (issue #21).
Client (packages/client-runtime/src/connection/supervisor.ts):
- Classify a setup timeout that reached the backend (stage
opening/synchronizing) as "slow, not dead": the next attempt gets a
45s establishment budget and a fast 1s retry instead of climbing the
exponential ladder. Timeouts that never reached the backend keep the
existing 15s budget + exponential backoff.
- Bound the tolerant path to MAX_TOLERANT_TIMEOUTS (3) consecutive slow
timeouts, then fall back to normal backoff — the stages are reported
before the socket is confirmed open, so a genuinely unreachable
endpoint must not loop on 45s attempts forever.
- Add a 30s background liveness probe (monitorConnectedLease) so a
wedged-but-open socket is detected without waiting for foreground
activation; the foreground probe was refactored into a shared helper
that also handles a relay credentials-change arriving mid-probe.
Desktop (apps/desktop/src/backend/DesktopBackendConfiguration.ts):
- Give the backend --max-old-space-size=4096 heap headroom (appended to
NODE_OPTIONS for the Electron-run-as-node primary, passed as a V8
execArg for WSL).
Server-side WS keep-alive/idleTimeout is deferred (the Effect beta
transport exposes no idle option and the RPC client pinger already
provides client->server liveness) — documented in the PR.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 55 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
… drop Follow-up to #25 (closes the last review nit on #21). The adaptive reconnect budget added in #25 arms a "tolerant" 45s establishment timeout after a backend-reached slow timeout. Three of the four run-loop branches that step away from an attempt already reset that budget, but the `network === "offline"` branch did not. A network drop is not backend slowness, so carrying the enlarged budget across the outage let the first attempt after the network returned wait 45s instead of the normal 15s. Reset `tolerant`/`consecutiveSlowTimeouts` in the offline branch (keeping `failureCount` so backoff continuity survives a transient blip) and add a regression test that arms the tolerant budget, blips offline→online, and asserts the reconnect times out on the normal 15s budget. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What & why
Fixes #21 — on a memory-constrained host the desktop app's local ("This device") backend intermittently shows
<machine> is not connected/ "reconnecting…" and is slow to recover, even though the backend process stays alive. This addresses the app-side robustness gaps.While mapping the code I confirmed the issue's root-cause analysis with one important refinement: the transport is Effect's
RpcServer/RpcClientover WebSocket (not a rawwsserver), and the RPC client already sends its own ~5s ping and fails a silent socket on ping-timeout. So client→server dead-socket detection already exists. The genuine, symptom-causing gap is that the reconnect supervisor is too impatient: under host contention the backend answers slowly, and every attempt is abandoned at a fixed 15s budget and then backed off — so a briefly slow but alive backend is repeatedly given up on. That's what this PR fixes.Changes
1. Client: tolerant / adaptive reconnect + background heartbeat — the core fix
packages/client-runtime/src/connection/supervisor.tsConnectionAttemptStageit reached. A setup timeout that reached the backend (opening/synchronizing) is treated as slow, not dead: the next attempt gets an enlarged 45s establishment budget (vs 15s) and retries after a fixed 1s instead of climbing the exponential ladder. A timeout that never reached the backend keeps the existing 15s budget + exponential backoff. Every non-timeout failure (transport/network/etc.) is unchanged.MAX_TOLERANT_TIMEOUTS = 3consecutive slow timeouts, then falls back to the normal budget + exponential backoff. (The stages are reported before the socket is confirmed open, so a genuinely unreachable endpoint could be misclassified as "slow" — the cap ensures it can't loop on 45s attempts forever and still surfaces backoff.)runLivenessProbehelper — which now also handles a relaycredentials-changedthat lands mid-probe (previously dropped until the probe finished).2. Backend: V8 heap headroom
apps/desktop/src/backend/DesktopBackendConfiguration.ts--max-old-space-size=4096— appended toNODE_OPTIONSfor the primary (Electron-run-as-node, which honours theNODE_OPTIONSsafelist), preserved if an operator already set one; passed as a V8 execArg for the WSL backend.Deliberately deferred (documented, not dropped)
Server-side WS keep-alive /
idleTimeout(issue's proposed fix #1). The Effect beta'sRpcServer.toHttpEffectWebsocketexposes no idle/ping option, and neither the Bun nor Node platform wrappers surface a WebSocketidleTimeout/ping-interval — a correct server heartbeat would need library/platform plumbing (or a wrapper around the upgraded socket). Given:…the server-side change is low marginal value for this symptom and too risky to land autonomously on the critical transport. The safest future approach is TCP keep-alive on the upgraded socket (Node backend). Happy to do it as a separate PR if you want it.
Tests
supervisor.test.ts(+9 tests): enlarged budget after a slow timeout; fast non-escalating retry; a slow backend connecting within the enlarged budget; theopening-stage boundary; the negative branch (apreparing-stage timeout stays on the 15s budget + exponential backoff); the tolerant cap falling back to exponential; the periodic background heartbeat; heartbeat-timeout → reconnect; relay credentials-change during a background probe. Full client-runtime suite: 479 passing.DesktopBackendConfiguration.test.ts: primaryNODE_OPTIONSheap flag, WSL execArg, and NODE_OPTIONS empty/append/preserve branches. Desktop backend suite: 34 passing.Review
This was put through an adversarial multi-lens review (concurrency, reconnect logic, backend config, test rigor) with per-finding verification. Confirmed findings were addressed: the tolerant-path cap (misclassified-unreachable-as-slow), the mid-probe credentials-change handling, and several test-coverage gaps (negative branch,
openingboundary, hermetic NODE_OPTIONS). The "4096 MB is miscalibrated" concern was refuted on verification (it's a ceiling, applied correctly). Ready for your review — I have not merged.Reviewer notes / decisions to sanity-check
SLOW_CONNECTION_ESTABLISHMENT_TIMEOUT=45s,SLOW_RETRY_DELAY_MS=1s,MAX_TOLERANT_TIMEOUTS=3,CONNECTION_HEARTBEAT_INTERVAL=30s,DESKTOP_BACKEND_MAX_OLD_SPACE_MB=4096.main.🤖 Generated with Claude Code