Skip to content

fix(basichost): fix some bugs in addrs reachability manager - #3528

Merged
sukunrt merged 8 commits into
masterfrom
sukun/autonatv2-fix
Jul 27, 2026
Merged

fix(basichost): fix some bugs in addrs reachability manager#3528
sukunrt merged 8 commits into
masterfrom
sukun/autonatv2-fix

Conversation

@sukunrt

@sukunrt sukunrt commented Jul 22, 2026

Copy link
Copy Markdown
Member

These are all minor except 7ccb3cd which may cause a panic on close.

All the commits are meaningful. See the commits for individual issues.

sukunrt added 4 commits July 22, 2026 23:41
The "host addresses updated" debug log is guarded by a change in
current.addrs but printed current.localAddrs, showing an unchanged
list whenever only relay addrs or the addrs factory output changed.

Assisted-By: Claude Fable 5
assignPrimaryAddrs matched ma.P_WEBRTC, which never appears in
thin-waist addrs, so /webrtc-direct scored 0 and was classified as
secondary only via the unknown-protocol fallback. Match the protocol
actually used. No behavior change for the default transport set; ties
against other unknown-protocol addrs now resolve deterministically.

Assisted-By: Claude Fable 5
…lation test

The "quits on cancellation" subtest constructed the tracker without
maxConcurrency, so refreshReachability spawned zero workers, the mock
client was never called, and the test passed vacuously.

Assisted-By: Claude Fable 5
Close set an.peers = nil without holding an.mx while GetReachability
reads it under the lock: an unsynchronized write, and a nil pointer
panic in peersMap.Shuffled for callers racing with Close. The host
closes autonat before the address manager, so the reachability
tracker's probe workers can issue checks in exactly that window,
crashing the process during shutdown.

Guard the write with the mutex and return ErrNoPeers once closed;
the reachability tracker treats ErrNoPeers as persistent and backs
off its workers.

Assisted-By: Claude Fable 5
@sukunrt
sukunrt requested a review from lidel July 22, 2026 18:14
…ries

Secondary addresses (webrtc-direct, webtransport, wss sharing a socket
with their thin-waist primary) refreshed every 3h while primaries
refreshed every 1h. Probe results expire after maxProbeResultTTL
(5 * 1h = 5h), which holds enough outcomes across a 1h cadence but not
a 3h one: a secondary needs two live outcomes to stay above
minConfidence, i.e. TTL >= 2 * refreshInterval. At 3h that is 6h > 5h,
so a confirmed secondary periodically expired down to a single outcome
and flapped to Unknown for up to a refresh-ticker interval every ~5h,
emitting spurious reachability-change events and, for a confirmed-
unreachable secondary, briefly re-advertising it in Addrs().

The 3h interval is vestigial. It was added in #3356 when secondaries
were always probed (once, when the primary was confirmed) as a way to
probe them less. #3435 then made secondaries inherit Public from their
primary and skip probing entirely in that case, so the only secondaries
still probed are those with a non-Public primary - exactly the ones we
want kept fresh. The reduced-cadence rationale no longer applies.

Drop highConfidenceSecondaryAddrProbeInterval and refresh every probed
address at highConfidenceAddrProbeInterval, which collapses the
primary/secondary branch in requiredProbeCountForConfirmation.

Assisted-By: Claude Fable 5
@sukunrt
sukunrt force-pushed the sukun/autonatv2-fix branch from f254253 to f667f33 Compare July 22, 2026 18:24
lidel added 3 commits July 24, 2026 13:27
The comments on targetConfidence and maxRecentDialsWindow pointed at
maxProbeInterval, which doesn't exist in the tree. The interval they
describe is highConfidenceAddrProbeInterval.
GetReachability unlocked an.mx by hand before the network call, so a
panic inside the critical section would leave the mutex held and any
later Close() would block forever on an.mx.Lock, hiding the original
panic behind a test-suite timeout. Extract the locked section into
pickServer with a deferred unlock so a panic unwinds with the mutex
released and surfaces as itself.
The "quits on cancellation" subtest raced cancel() against worker
startup and consistently lost: every worker returned at the top-of-loop
ctx check and the mock client was never entered, so cancellation of a
running probe was never exercised. Park all maxConcurrency workers
inside the client (one addr each), cancel only once every probe is in
flight, and assert the probe count from the test goroutine once the
workers are done.

@lidel lidel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed two follow-ups from review:

  • 7ba8874 refactor(autonatv2): unlock pickServer via defer
    so a panic inside the locked section can't leave an.mx held and wedge Close()

  • fad4253 test(basichost): cancel in-flight refresh probes
    cancel() used to win the startup race, so the mock client was never entered; the subtest now parks every worker inside the client before cancelling

Otherwise, fixes look solid. @sukunrt LGTM, if it looks ok to you too, feel free to merge.

@sukunrt
sukunrt merged commit bb0dc7d into master Jul 27, 2026
11 checks passed
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.

2 participants