[repo] e2e harness: piper-edge in front of two relays; ladder for the slot-1 boot race - #549
Merged
Conversation
Slot 1 dials the instant slot 0's handshake returns, but the relay acks before it writes the owner row the edge places on, so the edge can send slot 1 to the relay that already holds slot 0. Seen in production (16 ms margin) and on every loopback run. The slot then slept duplicateBackoff: a minute with no second session, which is a minute a relay roll drops the box. The duplicate wait is now a ladder: duplicateFirstRetry (2 s), then x4 up to duplicateBackoff (1 min). Slot 0 keeps its constant 5 s retry; a genuine one-relay pool still settles to the cap. Closes #548 Part of #537 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every relay e2e spawned a single piper-relay on :7000 and pointed piperd straight at it, a topology nobody runs since #530. startCluster in main_test.go now starts piper-edge on the public ports and two piper-relay processes on private ports in two zones, sharing one relaytest database; piperd dials the edge. The four binaries are built once per test process instead of once per test. TestRelayCluster asserts what the single-relay harness could not: the box's two sessions land on two relays in two zones, a visitor is still served while one relay is drained, and the stranded slot re-places when the relay returns. It is the test that found #548. Part of #537 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ladder Part of #537 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ozykhan
force-pushed
the
ozykhan/e2e-edge-two-relays
branch
from
September 5, 2026 21:45
770de92 to
802e5ff
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #537
Closes #548
Harness (#537)
Every relay e2e spawned one
piper-relayon:7000and pointedpiperdstraight at it — a topology nobody runs since #530. Now:startClusterintest/e2e/main_test.gostartspiper-edgeon the public ports (:8443/:8880/:7000) and twopiper-relayprocesses on private ports (1xxxx/2xxxx) in zoneszone-a/zone-b, sharing onerelaytestdatabase, withPIPER_RELAY_PROXY_PROTOCOL=1andPIPER_RELAY_ADVERTISE_HOST=127.0.0.1as a real deployment behind an edge needs.piperddials the edge and never learns a relay's address.TestRelayLoopback50 s → 1.4 s).piper loginstill dials one relay's API port directly: the edge routes the API by TLS SNI only, which a CLI can't reach over plain HTTP against a self-signed cert. State is in shared Postgres so either relay answers the same.TestRelayCluster: two owners in two zones → SIGTERM the relay holding slot 0 → visitor still served through the survivor → restart it (fresh instance id) → back to two owners.Bug it found (#548)
TestRelayClusterfailed on its first assertion, and the failure was real: slot 1 dials the instant slot 0's handshake returns, but the relay acks (tunnel.Serve) before it writes the owner row (acceptTunnels→SetOwner), so the edge places slot 1 on the relay that already holds slot 0 and it sleepsduplicateBackoff— a full minute with no second session. Confirmed in this Mac's production log against Hetzner: race lost with a 16 ms margin on the 20:48 UTC boot, won by 98 ms on the 19:27 one. Details and evidence in #548.Fix: the duplicate wait is a ladder —
duplicateFirstRetry2 s, ×4, capped at the existing 1 min. Slot 0 keeps its constant 5 s. A lost race now costs 2 s; a genuine one-relay pool still settles to the cap. Unit tests for the ladder and the lost-race recovery; the e2e log shows it end to end:Not touched: the relay-side ordering (ack after
SetOwner) that would close the race at the source — noted in #548 as a possible follow-up.Verification
make verifygreen (gofmt, vet,go test ./..., arm64 cross).make e2e: 11/11 pass in 117 s.🤖 Generated with Claude Code