Problem
DWNControl.UpdateEndpoints now takes ownership of the caller slice, but it still starts one independent 30-second goroutine for every magicsock callback. Endpoint churn can therefore create concurrent DWN writes, publish stale snapshots out of order, amplify rate limits, and leave work running across shutdown.
Target design
- one lifecycle-owned publisher worker per DWNControl
- synchronous deep-copy on enqueue
- bounded latest-wins queue with canonical endpoint/disco-key fingerprint dedupe
- serialized writes with a minimum cadence and Retry-After-aware backoff
- newer snapshots supersede pending stale snapshots
- shutdown cancels and waits for the worker
- observable publish/retry/drop/coalesce counters
Acceptance criteria
- no untracked goroutine per callback
- no concurrent endpoint writes
- caller mutation cannot race with publication
- bursts publish only the latest distinct snapshot
- failures retry without blocking control refreshes
- shutdown leaves no publisher work behind
- deterministic race tests cover dedupe, replacement, retry, cancellation, and disco-key changes
Problem
DWNControl.UpdateEndpoints now takes ownership of the caller slice, but it still starts one independent 30-second goroutine for every magicsock callback. Endpoint churn can therefore create concurrent DWN writes, publish stale snapshots out of order, amplify rate limits, and leave work running across shutdown.
Target design
Acceptance criteria