Skip to content

fix(relay): end a socket once when its peer vanished without a close - #2844

Merged
devswha merged 3 commits into
mainfrom
perf/relay-hibernation
Nov 5, 2026
Merged

devswha merged 3 commits into
mainfrom
perf/relay-hibernation

Conversation

@devswha

@devswha devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Change

A phone or PC that drops off the network without closing its relay socket (app suspended, Wi-Fi gone) never answers the close the relay sends it. Cloudflare keeps listing such a socket in getWebSockets() until the connection times out, about 15 minutes. A Durable Object instance woken after hibernation did not know the room had already ended that socket, so every idle alarm (about every 2 minutes):

  • closed it again,
  • sent the PC another {"type":"disconnected"} for the same connection_id,
  • wrote its usage report again,
  • and kept the room in memory from one alarm to the next instead of hibernating.

The room now records ended in a socket's attachment before closing it. peers() skips such sockets, and a later webSocketClose/webSocketError for one is a no-op. A failed send ends the socket through the same path. The wire protocol, close codes, limits and end-to-end encryption are unchanged.

Evidence

Cloudflare analytics for the hosted relay on 2026-11-04: of 125.7 hours of Durable Object active time, 116.9 hours (93%) were alarm invocations that each ran 110-120 s, in 10-60 minute streaks per room. The most expensive room was active for 6.8 hours with 1.2 s of CPU.

Reproduced on a throwaway Worker running this relay (same account, workers.dev), with a PC on the host and a phone in a Docker container whose network was disconnected silently:

before after
alarm wall time after the phone vanished 110-135 s each, starting about 4 minutes later 0 s
room active time 60 s per minute 0
disconnected heard by the PC 3 (01:42, 01:44, 01:46) 1

Healthy peers (idle PC, a pair with traffic, SIGSTOPped peers with TCP still up) never showed it, before or after.

Validation

  • bun test ./relay: 65 pass. The new test (a socket closed to a peer that vanished stays ended across an eviction) fails on main: the socket was closed twice.
  • bun run check fast: passed.
  • Skipped: check full, native Windows (relay-only change).

A phone or PC that drops off the network never answers the relay's close,
so Cloudflare keeps listing the socket until the connection times out. A
Durable Object instance after hibernation did not know the room had ended
it: every idle alarm closed it again, told the PC the client disconnected
again and reported its usage again, and the room stayed in memory between
alarms instead of hibernating.

The room now marks a socket ended in its attachment before closing it, and
skips sockets marked so.
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 44 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between a1de8d2 and a88816f.

Files selected for processing: 4

  • changelog.d/relay-vanished-peer.md
  • relay/core.ts
  • relay/worker.test.ts
  • relay/worker.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

…nded

A frame a vanished peer sent before the close can still be delivered after
the Durable Object woke again; it is no longer passed on, and ending the
socket again sends no second disconnected or usage report.
@devswha

devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Reviews of this PR:

  • omo review at 5214999: CLEAR.
  • Codex (adversarial) at 5214999: one MEDIUM, reproduced and fixed in a88816f. A frame a vanished peer sent before the close, delivered after the Durable Object woke again, reached message(), and an oversized one ended the socket a second time (a second disconnected and usage report). message() now drops frames from a socket an earlier instance ended, and end() returns for one. The vanished-peer test covers both and failed before the fix.

Validation at a88816f: bun test ./relay 65 pass; bun run check fast passed.

@devswha

devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Merge note: relay change, owner-delegated. omo CLEAR at this head, Codex findings reproduced and fixed, review threads resolved, required checks green. The relay deploy follows with the other relay PRs.

@devswha

devswha commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

Merge note: relay hibernation fix (vanished peers no longer keep rooms awake; 93% of DO active time on 11-04). Reproduced before/after on a test Worker. omo CLEAR at this head, Codex MEDIUM fixed, required checks green. Relay deploy follows.

@devswha
devswha merged commit 7da1003 into main Nov 5, 2026
12 checks passed
@devswha
devswha deleted the perf/relay-hibernation branch November 5, 2026 03:05
devswha added a commit that referenced this pull request Nov 5, 2026
Keeps both: the PC's early WELCOME is now marked provisional. A HELLO that
offers pairing by comparing digits (#2696) gets a second, final WELCOME with
what the PC selects, and a phone comparing digits waits for that one. Warm
sockets use the room's end() with #2844's ended mark, and a bound warm socket
goes through the PC's shared data-socket path with the pairing deadline.
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