Skip to content

Draft-20 backlog: relay shutdown and lifecycle - #115

Merged
floatdrop merged 3 commits into
draft-20from
draft20-backlog-shutdown
Sep 27, 2026
Merged

floatdrop merged 3 commits into
draft-20from
draft20-backlog-shutdown

Conversation

@floatdrop

Copy link
Copy Markdown
Owner

Batch 1 of the remaining draft-20 backlog: relay shutdown and lifecycle. One commit per item, each reviewed by moqt-reviewer, each with a regression test verified red first (by running it before the fix, or with a mutant where the fix changes a signature).

Commits

  • 427de7f Close the sessions Start's ctx ends, so Stop cannot hang on them.
    • Start documents cancelling its ctx as terminating live sessions. In practice it only ended each handler; the session was dropped from the set Stop closes and left open. A relay-scoped reader of an upstream SUBSCRIBE on it then never returned, so Stop waited without bound.
    • handleConn now closes the session with NO_ERROR (§3.5) as soon as the ctx ends (via context.AfterFunc), and again after serveSession returns if the ctx has ended, because stop can otherwise win the race.
    • Run is unaffected.
  • 39e4f13 Bound a straggler session's drain by Stop's ctx.
    • A session registering after Stop's snapshot now stops waiting when Stop's ctx ends, as the bulk drain already did, and closes with NO_ERROR (§3.5).
    • beginShutdown(ctx) records the ctx in place of the shuttingDown flag.
  • 942585a Refuse with GOING_AWAY when the only upstream relay is draining (§10.4, §10.6.2).
    • resolveUpstreams now also reports a relay it skipped for GOAWAY. subscribeUpstream counts that as a GOING_AWAY answer, ranked with the other "no publisher yet" errors.

Decisions and interpretations

  • Start's ctx cancel closes the sessions, rather than leaving them for Stop or merely bounding Stop's wait. This was a maintainer decision; the code now matches the existing doc.
  • Spec interpretation: GOING_AWAY is taken to outrank §10.2.6's DOES_NOT_EXIST for "no publisher is available" when the publisher is known but draining, because GOING_AWAY tells the client to retry. This is marked in the code.

Known gaps (added to STATUS.md)

  • Among "no publisher yet" answers the last one sets the code, so candidate order matters: a draining local publisher plus a remote DOES_NOT_EXIST gives DOES_NOT_EXIST, and the reverse gives GOING_AWAY.
  • An upstream relay's own GOING_AWAY answer, sent before its GOAWAY reaches this relay, is passed on as INTERNAL_ERROR.

Verification

  • go test ./... and golangci-lint run pass.
  • go test -race ./pkg/relay/... passes.
  • TestRelay_StartCtxClosesSessions was stressed with 5×20 runs under -race.

🤖 Generated with Claude Code

floatdrop and others added 3 commits September 27, 2026 19:20
…on them

Start documents cancelling its ctx as terminating live sessions, but it only
ended each session's handler: the session was unregistered, so Stop no longer
closed it, and left open. A relay-scoped reader of an upstream SUBSCRIBE on
such a session then never returned, and Stop waited on it without bound. A
handler with an inbound subgroup stream open did not even end, so its
session stayed open and registered.

handleConn now closes the session with NO_ERROR (§3.5: no GOAWAY was sent,
so none ran out) as soon as Start's ctx ends, via context.AfterFunc, and
again once serveSession returns if the ctx has ended: stop can otherwise win
against an AfterFunc that has not started yet. Run is unaffected: it hands
Start a ctx that is never cancelled. Pooled upstream sessions run under the
pool's context and are closed by Stop as before.

TestRelay_StartCtxClosesSessions, idle and with a publisher's subgroup
stream open, was verified red before the change. Closing only after
serveSession returns fails the open-stream case every time. A bare
`defer stop()` failed the idle case intermittently under -race with
-count=20.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A session that registers after Stop took its snapshot drains on its own:
GOAWAY, grace period, force-close. That wait ignored Stop's ctx, so a
cancelled Stop could still wait up to GoawayTimeout for it, although Stop's
bulk drain cuts short on the same ctx.

beginShutdown now records Stop's ctx in place of the shuttingDown flag, and
drainStraggler also ends on it, closing with NO_ERROR: the grace period did
not run out, so GOAWAY_TIMEOUT does not apply (§3.5), as in the bulk drain.

TestRelay_addSessionDrainsStraggler gains a case with a one-hour grace
period and Stop's ctx ending at 100ms; it fails with the ctx case removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…aining (§10.4)

A SUBSCRIBE whose only candidate was a relay reached through the upstream
pool that had sent GOAWAY got DOES_NOT_EXIST: the pool skips such a relay
before any request (§10.4), so no answer of its own existed. A draining
local publisher yields GOING_AWAY ("The endpoint has received a GOAWAY",
§10.6.2).

resolveUpstreams now reports whether it skipped a draining relay, and
subscribeUpstream counts that as a GOING_AWAY answer, ranked with the other
"no publisher yet" errors; under RENDEZVOUS_TIMEOUT the hold continues, as
before.

Interpretation, marked in the code: GOING_AWAY is taken to outrank §10.2.6's
DOES_NOT_EXIST for "no publisher is available", since the publisher is known
and GOING_AWAY says to retry. The last "no publisher yet" answer still sets
the code, so the order of candidates matters there; that, and an upstream
relay's own GOING_AWAY answer being passed on as INTERNAL_ERROR, are
recorded in STATUS.md.

TestCrossRelay_DrainingUpstreamRelayAnswersGoingAway (alone, and with a
local publisher refusing too) uses a Discovery store that keeps a draining
relay listed, as an eventually-consistent backend may; it fails with the
handler change removed. TestResolveUpstreamsSkipsGoingAwayRelay asserts the
new result.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@floatdrop
floatdrop merged commit a1a6a61 into draft-20 Sep 27, 2026
11 checks passed
@floatdrop
floatdrop deleted the draft20-backlog-shutdown branch September 27, 2026 15:11
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