Skip to content

feat(relay): defer binary release with 30s grace window (#21)#23

Merged
ilmoniemi merged 3 commits into
mainfrom
feature/21
May 8, 2026
Merged

feat(relay): defer binary release with 30s grace window (#21)#23
ilmoniemi merged 3 commits into
mainfrom
feature/21

Conversation

@ilmoniemi
Copy link
Copy Markdown
Contributor

What

Swaps the /v1/server disconnect-cleanup defer from the immediate Registry.ReleaseServer to Registry.ScheduleReleaseServer, threading a grace duration through ServerHandler's constructor. Production wires 30*time.Second per protocol spec § Authentication → Binary → relay. After this change, a binary that disconnects (clean close, network error, ping timeout) holds its server-id slot for the grace window; a reconnect within that window lands back in the same slot atomically (registry-side reclaim from #20).

The wsconn.Close() and server_released log line are unchanged. The defer registration position is unchanged (still after the successful ClaimServer, per docs/lessons.md:21-23).

Issue

Closes #21. Spec: docs/specs/architecture/21-defer-binary-release-grace-window.md. Builds on #20 (Registry.ScheduleReleaseServer).

Testing

  • startServer test helper now takes grace time.Duration; all existing call sites pass 100*time.Millisecond (none of them rely on the disconnect timing).
  • TestServerEndpoint_PeerClose_ReleasesSlot continues to pass — the existing 2-second wait window comfortably covers the 100 ms grace.
  • New TestServerEndpoint_PeerClose_SchedulesGraceRelease (grace = 200 ms): asserts the binary entry persists for grace/4 after the peer close (proves scheduled, not immediate) and is gone within 2*grace + 500 ms (proves the timer fires). Inspects registry state directly via BinaryFor, since ScheduleReleaseServer exposes no return value.
  • go test -race ./... and go vet ./... clean.

Architecture compliance

  • Constructor-parameter approach for the grace duration, per the spec's "constructor parameter (chosen)" rationale — policy lives at the wiring site, not buried in a package-level var.
  • No validation of grace: trusted from the wiring site (compile-time literal in main.go); the registry tolerates degenerate values safely (relay: registry — schedule deferred binary release with grace-period reclaim #20 contract).
  • No new goroutines; no changes to the header gate, conflict path (4409), connID generation, or the CloseRead hold-open block.
  • Per the spec's security review: no new untrusted-input boundary, defer ordering preserved, Schedule → Close → Log mirrors the old Release → Close → Log order.

🤖 Generated with Claude Code

ilmoniemi and others added 2 commits May 8, 2026 22:51
Swap the /v1/server disconnect-cleanup defer from the immediate
Registry.ReleaseServer to ScheduleReleaseServer, threading a grace
duration through ServerHandler's constructor. Production wires
30*time.Second per protocol spec § Authentication → Binary → relay;
tests use short durations to exercise the scheduled-release path
without slowing the suite.

A reconnect within the grace window now lands in the same server-id
slot atomically (registry-side reclaim from #20). On expiry, orphan
phones for that server-id are closed by the registry's expiry handler.

Adds TestServerEndpoint_PeerClose_SchedulesGraceRelease asserting
the binary entry persists for grace/4 after close (proves scheduled,
not immediate) and is gone within grace + slack (proves the timer
fires).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@ilmoniemi
Copy link
Copy Markdown
Contributor Author

Code Review: #21

Decision: PASS

Findings

None.

Summary

Clean call-site swap that lands exactly the spec laid out. Verified:

  • internal/relay/server_endpoint.go:34ServerHandler gains the grace time.Duration third parameter with a doc comment that names the protocol-spec source for 30*time.Second.
  • internal/relay/server_endpoint.go:86-90defer swaps ReleaseServerScheduleReleaseServer(serverID, grace). Position is preserved (after the success-log line, after ClaimServer succeeds), so the docs/lessons.md:21-23 invariant ("defer ReleaseServer(...) must be registered AFTER the successful ClaimServer") still holds — the conflict path returns at line 72 before the defer arms a stray timer. wsconn.Close() and the server_released log line are unchanged, as required.
  • cmd/pyrycode-relay/main.go:50 — passes 30*time.Second; time was already imported.
  • internal/relay/server_endpoint_test.gostartServer helper takes grace, and all 5 ServerHandler(...) call sites (3 via helper + 2 inline in TestServerEndpoint_HeaderGate_400 / TestServerEndpoint_WrongMethod_NoPanic) updated. Existing four tests' behaviour is unchanged.
  • New TestServerEndpoint_PeerClose_SchedulesGraceRelease (line 251) uses the two-phase assertion the spec described: grace/4 early window proves "not immediate," 2*grace + 500ms late window proves "timer fires." Direct reg.BinaryFor inspection per AC.

Local go vet ./... and go test -race ./... -count=1 both clean.

Security goggles (ticket carries security-sensitive)

Spec at docs/specs/architecture/21-server-endpoint-grace-release.md:243-293 contains the required ## Security review section with verdict PASS and findings explicitly tracked. Walked the diff for the standard categories:

Implementation matches the spec's security findings; no architect-side gaps surfaced.

Update server-endpoint feature doc, PROJECT-MEMORY, lessons, and INDEX to
reflect the constructor-parameter grace duration, the ScheduleReleaseServer
swap on disconnect, and the sharpened defer-after-claim invariant.
@ilmoniemi ilmoniemi merged commit 7e3cedc into main May 8, 2026
2 checks passed
@ilmoniemi ilmoniemi deleted the feature/21 branch May 8, 2026 20:00
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.

relay: /v1/server — defer binary release by 30s grace window

1 participant