Documentation
¶
Overview ¶
Edge pull (R17 W4, docs/22 Decisions 9/10/12): when a viewer lands on a pod that is not a broadcast's origin, the pod subscribes upstream — dialing the origin's POD IP from the Lease (never the Service VIP: guard 1 against loops) over the same WebTransport wire protocol — and re-ingests everything into a local EDGE hub through the ordinary Publisher surface: datagrams verbatim, keyframe streams byte-identical, so store-and-forward + supersede compose per hop. Local viewers attach to that hub exactly as they would on the origin.
The one thing that is NOT forwarded verbatim is the ClockMapping (Decision 12): each pod keeps its own monotonic clock, so the edge runs a Go port of the client TimeSync estimator over its upstream session and rewrites the mapping's offset from origin-clock terms into edge-clock terms before it reaches local viewers.
Resume tokens (R17 W2, docs/22 Decision 7): the second gate on /publish/{id}. A token is a truncated HMAC over the normalized broadcast ID, keyed by a fleet-shared key every relay pod holds — so any pod can mint and verify with no shared storage, which is what lets a broadcaster claim its ID on a pod that has never seen it (the pod then *creates* the hub instead of 404ing, making broadcasts survive relay restarts).
Hijack scope, stated honestly (revised in the PR #47 security review): with an explicit -resume-token-key — which never leaves the server side — knowing a broadcast ID plus the global publish secret no longer suffices to take over someone else's broadcast; that closes the pre-W2 graced-ID hijack for real. With only a publish secret, the token key is DERIVED from it, so every secret-holder can compute every ID's token offline — that mode still stops everyone who lacks the secret, but gates nothing between broadcasters. Fleet deployments should set the explicit key (docs/05 runbook).
Tokens are never logged.
Package transport owns the HTTP/3 + WebTransport endpoint: routes, session acceptance and server lifecycle. It contains no media logic — datagrams are handed to the hub as opaque bytes.
Telemetry hello (R28 TM1, docs/33 D2 + §4.1): the relay's half of the correlation ID.
/statusz has always named a subscriber by a random per-session key and never told that client its own key, so the relay's view of a viewer and the viewer's view of itself were two datasets that could not be joined — "per-viewer experience" is exactly that join, and closing it is what the rest of R28 is built on.
The hello rides a reliable unidirectional stream, following ResumeToken (0x09) rather than DeliveryAck (0x0C). DeliveryAck picked a datagram and had to grow a re-announce loop because a single join-time datagram gets lost at exactly the moment a client is least likely to be draining its queue; a lost hello is worse than a mislabelled row — it is a session that silently never reports at all, which is indistinguishable from a viewer who never showed up.
Sending it is best-effort in one specific sense: a failure to open or write the stream must never take down a working broadcast. Telemetry that can degrade a stream has failed on its own terms (docs/33 D9).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ClusterCoordinator ¶
type ClusterCoordinator interface {
// Claim acquires the broadcast's origin Lease for this pod. force is set
// when the claimant presented a valid resume token (every /publish/{id});
// mint-path claims are create-only (never steal a live holder).
Claim(ctx context.Context, broadcastID string, force bool) (int64, error)
// ReleaseAll clears this pod's lease holderships (the SIGTERM drain) so
// the broadcaster's instant reconnect can claim on another pod.
ReleaseAll(ctx context.Context)
// Resolve returns the broadcast's current origin (edge pull, W4).
Resolve(ctx context.Context, broadcastID string) (cluster.Origin, error)
// OriginGeneration reports whether this pod holds the broadcast's lease
// and at which generation — the internal route's 404/409 fence (W4).
OriginGeneration(broadcastID string) (int64, bool)
}
ClusterCoordinator is the transport's slice of *cluster.Coordinator (R17 W3/W4, docs/22 Decision 8). Nil = single-pod mode: no claims, no releases, no edge pulls — behavior byte-identical to pre-R17.
type EdgeManager ¶
type EdgeManager struct {
// contains filtered or unexported fields
}
EdgeManager owns this pod's edge pulls: at most one upstream session per broadcast, demand-created when a viewer asks for a hub we don't have, lingering ~15 s past the last local viewer, and torn down when the lease disappears (the Lease is the liveness truth — no grace, Decision 10).
func (*EdgeManager) EnsureEdge ¶
func (m *EdgeManager) EnsureEdge(ctx context.Context, broadcastID string) error
EnsureEdge makes sure an edge pull is running for the broadcast and its local hub exists, blocking (bounded) until the first upstream attach. A hub.ErrNotFound return maps to the viewer's 404: no lease, an origin in flux (empty holder mid-re-home), or a stale lease naming this very pod (guard 3: never dial ourselves).
func (*EdgeManager) OnLeaseDeleted ¶
func (m *EdgeManager) OnLeaseDeleted(broadcastID string)
OnLeaseDeleted tears down the broadcast's edge pull (if any) so its local viewers get the terminal 4000 from the registry's EndBroadcast — which the caller (main's OnLeaseDeleted dispatch) invokes right after this.
func (*EdgeManager) Stop ¶
func (m *EdgeManager) Stop()
Stop tears down every edge pull (server shutdown).
func (*EdgeManager) StopEdge ¶
func (m *EdgeManager) StopEdge(broadcastID string)
StopEdge synchronously stops the broadcast's edge pull, if any (lease deletion, or the W5 come-home: the real broadcaster claiming this pod needs the hub's publisher slot our upstream pull is holding).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server wraps a webtransport.Server with the gawk routes.
func New ¶
func New(cfg config.Config, r *hub.Registry, getCert func(*tls.ClientHelloInfo) (*tls.Certificate, error), log *slog.Logger, m *metrics.ServerMetrics) *Server
New builds the server. getCert supplies the TLS certificate per handshake (a tlsutil.Reloader in production, a fixed dev cert locally). m carries the connection-outcome counters and may be nil (tests).
func (*Server) HandleLeaseDeleted ¶
HandleLeaseDeleted is the cluster informer's lease-deletion dispatch (cluster-wide "broadcast ended"): stop any edge pull for the broadcast, then expire the local hub so viewers get the terminal 4000.
func (*Server) HandleLeaseLost ¶
HandleLeaseLost is the demote path (R17 W5, docs/22 Decision 11): this pod's Lease for the broadcast was force-taken — the broadcaster re-homed (NAT rebind / rollout reconnect) while our publisher session still looks half-alive. (a) Close that stale session (its client already abandoned it; only the new session drives resume, so no ping-pong). (b) Close downstream edge sessions with 4003 — the Go edge clients re-resolve to the new origin. (c) Become an edge ourselves for any still-connected local viewers: nobody chases viewers across pods, and depth stays ≤ 2 because the new origin serves us directly.
func (*Server) Ready ¶
Ready reports whether the server is accepting new work — false once the drain has begun. Served as /readyz on the ops endpoint; per docs/22 Decision 2 this is scale-down/HPA hygiene, not the rollout-correctness mechanism (that is the active drain above).
func (*Server) Run ¶
Run serves until ctx is cancelled, then drains (4002 to every open session, staggered ≤ drainWindow — R17 W1) and closes the server. It always returns a non-nil listen error, or nil after a graceful shutdown.
The QUIC transport is constructed explicitly (not via wt.ListenAndServe, which builds its own) so the shared StatelessResetKey can be set: with the key, any pod receiving packets for an unknown connection ID answers with a stateless reset the client accepts — abrupt pod death is detected in ~1 RTT instead of the ~30 s idle timeout (docs/22 Decision 3).
func (*Server) SetCluster ¶
func (s *Server) SetCluster(c ClusterCoordinator, podName string)
SetCluster wires the origin-Lease coordinator (R17 W3/W4): /publish claims acquire the broadcast's Lease, the SIGTERM drain releases this pod's holderships right after the 4002s go out — the broadcaster's instant reconnect then claims an empty-holder lease on a ready pod, no force or TTL wait needed — and viewers landing here for broadcasts we don't host trigger an edge pull from the lease's origin pod. podName is this pod's identity (the self-dial guard). Call before Run.