feat(p2p)+fix: cover-traffic stack + lifecycle/race closure (T1.5) - #40
Merged
Conversation
…oring, and Playwright tests - T4.1: WebRTC relay client with RTCPeerConnection + HTTP signaling - T4.2: TURN fallback — getTurnConfig() from /api/relay/turn-config - T4.3: Health monitoring — ping/pong every 10s, RTT tracking, loss detection, auto-degradation at RTT>2s or loss>20%, reconnect backoff - T4.T: Playwright spec with 10 tests covering constructor, STUN config, TURN fetch, disconnect cleanup, health status, and degradation
- T4.G: POST /api/relay/signal/offer — creates PeerConnection with STUN, sets remote description, creates data channel, returns SDP answer - T4.G: GET /api/relay/turn-config — returns STUN ICE server config - Routes registered in RegisterRoutes() - PeerConnection map with JWT session tracking and cleanup on disconnect - Data channel echoes ping/pong for health monitoring
…g hot-reload Phase 1 of openspec/archive/2026-07-14-integrar-28sp-en-mesh-hermes: * T1.1 seed-health gate: when no seed reachable, enter degraded mode (GossipSub-only, no DHT provider advertisements to prevent metadata leaks). * T1.2 concurrent discovery: mDNS and DHT run in parallel goroutines from Start(). * T1.3 DHT health monitor: 2 consecutive failures trigger degraded mode; automatic recovery re-enables DHT advertising on first success. * T1.4 cover traffic on dedicated /28sp/cover/v1 GossipSub topic (R2.3): dynamic rate via setRateCh, CPU-load throttle with single goroutine writer. * T1.5 config hot-reload: SHA-256 polling of config.yaml surfaces cover-traffic rate changes within 2s without restart. * T1.T topic isolation tests: cover messages stay on /28sp/cover/v1 and never reach the application handler subscribed on the main app topic. Concurrency safety hardening: * P2PHost.IsDegraded() and P2PHost.DHTConsecFails() thread-safe getters (degradedMu). * CoverTraffic.throttleEnd time.Time converted to throttleEndUnixNano atomic.Int64 to eliminate cross-goroutine race. * Race-safe tests: done-channel pattern for clean monitor exit, ctx-aware coverCh helper for T1.T isolation tests, single sub.Next to avoid double-read hang. * Dropped vestigial yaml tags on internal/covertraffic.Config (CTConfig.resolve() is the single source of truth). Verified clean on `go test -race ./internal/covertraffic/... ./internal/p2p/...`.
…n cover-traffic stack
Closes five correctness gaps uncovered by code review on the earlier
cover-traffic + DHT health monitor + config hot-reload bundle:
1. CPU heuristic: replace goroutine-count/GOMAXPROCS throttle (broken
under libp2p's idle baseline of 30-80+ goroutines that take zero
CPU) with a runtime.ReadMemStats GC-pause accumulator (computeLoad
helper, unit-testable). New metric only triggers when GC is actively
stealing CPU from the publisher.
2. discoveryCancel race: protect P2PHost.discoveryCancel with
discoveryMu sync.Mutex. Both Start() (initial spawn) and
monitorDHTHealth() (recovery branch) read+write the field under
the lock; cancel funcs themselves remain idempotent.
3. Single-shot Start: P2PHost.Start returns an explicit error when
called twice on the same host (previously silently no-op'd,
leaving dependents bound to a cancelled hostCtx). To restart,
construct a new P2PHost.
4. Cover lifecycle closure: add coverCtx/coverCancel fields bound
through InitCoverTraffic / StartCoverTraffic so P2PHost.Stop()
always reaches the cover run-loop, even when InitCoverTraffic +
StartCoverTraffic run BEFORE P2PHost.Start() (the orphan
scenario). The previous API bound to a caller-supplied ctx that
nothing cancelled.
5. Regression tests pin each invariant:
- TestCoverTraffic_ComputeLoad validates the GC-pause math
(empty/200ms clamped/50ms approx 0.5/short window/burst/256-cycle wrap)
- TestStart_RepeatStartReturnsError locks in single-shot semantics
- TestStop_TerminatesManualCoverWiring closes the orphan with a
pre-Stop publish-check
- TestP2PHost_StopDiscoveryCancelRaceClean verifies race-clean
concurrency under -race
Also adds coverMu to guard the coverCtx binding from concurrent
InitCoverTraffic callers (rare race; closes the orphan scenario for
the double-Init path). Error messages adjusted to Go convention
(lowercase first letter, no trailing punctuation).
Also includes the bounded seed-health probe (discovery.go) with
aggregate timeout, and the unreadable-config baseline reset
(hotreload.go) so a transient config-file disappearance doesnt
silently swallow re-fires.
All packages: go vet clean, go build clean, go test -race clean.
No follow-up TODO. Reviewed by code-reviewer-minimax-m3 (LGTM for
commit, one optional non-blocking follow-up: extend coverMu to
cover the entire InitCoverTraffic body -- accepted as a separate
small PR scope).
Extends p.coverMu.Lock() to wrap the entire body of InitCoverTraffic (previously only the late coverCtx binding was protected) and adds an idempotency check at function entry. A second concurrent Init caller now returns early instead of double-allocating coverTraffic, coverTopic, and coverCtx. Closes M_final-A1. Scope: write-side only. Reader-side (publishCover, Stop, AutoStartCoverTraffic) still accesses the fields unlocked; the current serial call path makes -race clean, but cross-method reader coverage is a documented follow-up. Tests: 17/17 PASS under -race; vet + build clean.
Adds .github/workflows/ci.yml that runs go vet, go build, and go test -count=1 -race -v -timeout=120s against ./... on every push to main and PR targeting main. Matrix strategy covers Go 1.25 and 1.26 (go.mod declares 1.25.11 floor; 1.22 and 1.23 — the originally suggested minimums — are rejected by Go's toolchain). Setup uses actions/setup-go@v5 with cache=true and cache-dependency-path: go.sum so dependencies are cached across runs. Concurrency group cancels superseded runs on the same ref to avoid wasted CI minutes. Workflow-level permissions are scoped to contents: read; expand only if future steps need write. Closes the no-CI gap on PR #40 — without this workflow the cover-traffic bundle would have landed without automated verification of the 17 tests on a non-author runner.
- web/relay-client.js: guard localStorage.getItem in getTurnConfig against unavailable storage (e.g. about:blank test pages). - tests/relay-client.spec.ts: mock window.fetch directly in browser context for getTurnConfig test; handle string/array TURN URL formats. - package.json: add test:relay, test:e2e, server:dev and server:node2 scripts.
Add an e2e job that builds the Go binary, starts two dev servers on ports 8080/8081, runs the Playwright suite, and uploads reports and server logs on failure.
- Add /health endpoint for CI/CD readiness probes (no auth). - Update GitHub Actions E2E job to wait on /health. - Update README with WebRTC relay, Playwright E2E, npm scripts, CI/CD and /health docs. - Fix Playwright static-asset URLs to use /static/ prefix. - Fix files.spec.ts selectors to match actual 28sp_FILES.html elements. - Fix p2p-files.spec.ts credentials to admin/admin (dev server defaults).
The root package's jwtSecretKey init calls log.Fatal when JWT_SECRET is missing, which kills go test ./... in CI. Add the env var at job level so all test steps have it available.
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.
Summary
Pull request introducing the cover-traffic bundle scoped under SDD
integrar-28sp-en-mesh-hermestask T1.5 (config hot-reload + cover lifecycle), with a follow-up atómico fixing 5 correctness gaps surfaced by code review, plus an additional single-flightInitCoverTrafficclosure.Commits
866259fce303226b354202466acb5 fixes (commit 6b35420)
goroutine-count/GOMAXPROCSthrottle (which throttled cover traffic permanently under libp2p's idle baseline of 30-80+ goroutines) with aruntime.ReadMemStatsGC-pause accumulator (computeLoadhelper). New metric only triggers when GC is actively stealing CPU.discoveryCancelrace — ProtectedP2PHost.discoveryCancelwithsync.Mutex. BothStart()(initial spawn) andmonitorDHTHealth()(recovery branch) now read+write under the lock.Start()returns an explicit error when called twice on the same host. To restart, construct a newP2PHost.coverCtx/coverCancelfields ensureStop()always reaches the cover goroutine, even whenInitCoverTraffic + StartCoverTrafficran BEFOREP2PHost.Start()(the orphan scenario).TestCoverTraffic_ComputeLoad,TestStart_RepeatStartReturnsError,TestStop_TerminatesManualCoverWiring(with publish-check),TestP2PHost_StopDiscoveryCancelRaceClean.Plus M_final-A1 (commit
ce30322): extendedcoverMufrom covering only the latecoverCtxbinding to covering the entire body ofInitCoverTraffic, with an idempotency check so concurrent Init callers cannot double-allocatecoverTraffic/coverTopic/coverCtx.CI workflow
This PR also adds
.github/workflows/ci.ymlthat runsgo vet ./...,go build ./..., andgo test -count=1 -race -v -timeout=120s ./...on every push and PR againstmain. Matrix covers Go 1.25 (the go.mod floor) and 1.26. Cache viaactions/setup-go@v5+cache-dependency-path: go.sum. Concurrency group cancels superseded runs on the same ref. Closes the no-CI gap on this bundle — without this workflow the 17 tests would only have been validated locally.Test plan applied
go vet ./...— cleango build ./...— cleango test -count=1 -race -v -timeout=120s ./internal/covertraffic/...— 10 / 10 PASSgo test -count=1 -race -v -timeout=120s ./internal/p2p/...— 7 / 7 PASSSDD reference
Task T1.5 of
openspec/changes/archive/2026-07-14-integrar-28sp-en-mesh-hermes/(cover traffic + config hot-reload). The 5-fix bundle resolves correctness gaps flagged in the SDD's "20/20 tasks complete" review.Known follow-ups (not in this PR)
M-fup-A):publishCover,Stop,AutoStartCoverTrafficstill readcoverTraffic/coverTopic/coverCtxunlocked. The current serial call path keeps-raceclean, but a future concurrent wiring would race. Tracked separately./tmp/archive/28sp.old-pre-fixes-*.bak(45MB pre-fix snapshot).