Skip to content

refactor(remote): drop the vacuous intentional-close check in PocketClient - #410

Merged
nedtwigg merged 2 commits into
mainfrom
fix/pocket-close-dead-guard
Aug 19, 2026
Merged

refactor(remote): drop the vacuous intentional-close check in PocketClient#410
nedtwigg merged 2 commits into
mainfrom
fix/pocket-close-dead-guard

Conversation

@dormouse-bot

Copy link
Copy Markdown
Collaborator

PocketClient.#onClose decides whether a closed relay socket should fire onHostGone, and it still computes that decision two ways. The generation guard added in 499123dd ("Fix stale Pocket socket events") already rejects every close that isn't the live socket's — an intentional close() nulls #ws before calling ws.close(), and a reconnect overwrites it — so by the time control reaches the next line, this.#ws === ws and this.#ws !== null is unconditionally true. The comment above it still explains the superseded null-check mechanism as the thing distinguishing an intentional close from a drop, which is no longer where that decision is made.

This drops the vacuous unexpected term and moves the comment onto the guard that actually does the work. No behavior change: notifyGone is now this.#connectedHostId !== null, which is what unexpected && hadSession already evaluated to on every reachable path.

No new regression test — this is a refactor with no behavior delta, and all four branches are already pinned by the existing socket lifecycle suite: unexpected drop with a session fires host-gone, intentional close() doesn't, an unexpected drop without a session is silent, and a stale socket's close is ignored entirely.

Verification

Checked against 92608bb5:

  • corepack pnpm --filter dormouse-lib exec vitest run src/remote/client/pocket-client.test.ts → 30 passed.
  • corepack pnpm --filter dormouse-lib exec tsc --noEmit → clean.
  • The four covering tests: pocket-client.test.ts#L661, #L673, #L696, #L707.
  • No spec text describes this internal branch (grep 'host-gone\|onHostGone' docs/specs/ only matches the Host-displacement paragraph in server.md), so nothing to update above the fold.

Surfaced by the nightly rolling survey.

…lient

The generation guard already rejects every close that is not the live
socket's, so the null check below it was unconditionally true and its
comment described a mechanism no longer in play.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 19, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: a300b39
Status: ✅  Deploy successful!
Preview URL: https://0b618eeb.mouseterm.pages.dev
Branch Preview URL: https://fix-pocket-close-dead-guard.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vacuousness argument holds: #onClose(ws: PocketSocket) takes a non-nullable socket, so past if (this.#ws !== ws) return; we have this.#ws === ws with ws non-null, making this.#ws !== null unconditionally true. notifyGone reduces to #connectedHostId !== null on every reachable path, and the four socket lifecycle tests pin all of them. The Host-side twin in remote-host.ts (if (this.#ws !== ws) return; inside its close listener) never grew the same redundant term, so there's no second instance to clean up.

One thing the change leaves behind. close() still carries the sibling comment describing the mechanism this PR just deleted:

// Tear down BEFORE closing the socket: #onClose reads `#ws === null` as an
// intentional close (no host-gone), and while real sockets emit their close
// event asynchronously, test fakes may emit it synchronously from close().

#onClose no longer reads #ws === null anywhere — the teardown-before-close ordering now matters because nulling #ws makes the generation guard reject the close, which after this PR is the sole thing keeping an intentional close() from firing onHostGone. That makes the ordering constraint more load-bearing than it was, while its only explanation now points at a check that isn't in the file. A reader who greps for #ws === null in #onClose and comes up empty could reasonably conclude the ordering no longer matters and move the ws?.close() above the teardown, which would resurrect the bug in the closeEmits synchronous-fake case the comment's second clause is warning about.

It's outside the diff, so I've pushed the comment rewrite as a follow-up commit rather than leaving an inline suggestion. Nothing else to flag — no behavior delta, and no spec covers this branch (docs/specs/server.md's only host-gone mention is the Host-displacement paragraph, which is about the relay, not the client's close handling).

close() tore down before ws.close() because #onClose read `#ws === null`
as an intentional close. That read is gone; the ordering now matters
because nulling #ws is what makes the generation guard reject the close,
which is the sole thing keeping an intentional close from firing
host-gone. Describe the mechanism that is actually there.
@nedtwigg
nedtwigg merged commit c0d3aef into main Aug 19, 2026
9 of 10 checks passed
@nedtwigg
nedtwigg deleted the fix/pocket-close-dead-guard branch August 19, 2026 14:22
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.

2 participants