Reachability is now recomputed ONLY when the active routing tree changes, not every idle tick. Per user direction: events decide WHO is reachable; the timer only checks WHEN (last-activity comparison). - adapter.ReachabilityInvalidator: narrow interface (not folded into the large adapter.Router), registered into ctx in box.go, pulled by groups via service.FromContext — no route<-group import. - Router: reachMu/reachCache/reachDirty. InvalidateReachability() is a lock-free atomic store (safe under any group lock — no lock-order cycle). reachableOutbounds() recomputes the walk OUTSIDE the cache lock (the walk calls into groups that hold their own locks), clears dirty BEFORE the walk so a concurrent event re-dirties for next tick rather than being lost, publishes under RWMutex. Starts dirty so the first tick (and every reload = fresh Router) computes. - 4 invalidation sources: selector switch (selector.go after selected.Store), legacy urltest auto-switch (urltest.go performUpdateCheck), and a balancer onChange hook fired from setSlots — one hook covers all pool-rebuild call sites. - idle tick: now one cached-map lookup + atomic idle compare per endpoint, no walk. Design independently verified against source (no data race, no import cycle, no deadlock — walk runs outside the lock). Builds + go vet + race-build clean.
24 lines
784 B
Go
24 lines
784 B
Go
// lx:begin idle-suspend
|
|
package group
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing/service"
|
|
)
|
|
|
|
// invalidateReachability tells the router (SPEC 020) that the active routing tree
|
|
// changed, so its cached reachable set must be recomputed. Called from the group
|
|
// event points — a selector switch, a urltest auto-switch, a pool rebuild. Pulls
|
|
// the invalidator out of the context the same way groups pull other services, so
|
|
// protocol/group never imports route. A nil invalidator (feature/route absent) is
|
|
// a no-op.
|
|
func invalidateReachability(ctx context.Context) {
|
|
if invalidator := service.FromContext[adapter.ReachabilityInvalidator](ctx); invalidator != nil {
|
|
invalidator.InvalidateReachability()
|
|
}
|
|
}
|
|
|
|
// lx:end idle-suspend
|