diff --git a/adapter/experimental.go b/adapter/experimental.go index 697fa9f1..87eb936c 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -43,7 +43,7 @@ type OutboundGroup interface { type URLTestGroup interface { OutboundGroup - URLTest(ctx context.Context, url string) (map[string]uint16, error) + URLTest(ctx context.Context) (map[string]uint16, error) } func OutboundTag(detour Outbound) string { diff --git a/adapter/router.go b/adapter/router.go index e4c3904d..3d18eb38 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -15,6 +15,7 @@ import ( type Router interface { Service + PostStarter Outbounds() []Outbound Outbound(tag string) (Outbound, bool) diff --git a/box.go b/box.go index c10222dd..3c0479c7 100644 --- a/box.go +++ b/box.go @@ -258,7 +258,7 @@ func (s *Box) start() error { return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") } } - return nil + return s.postStart() } func (s *Box) postStart() error { @@ -269,16 +269,17 @@ func (s *Box) postStart() error { return E.Cause(err, "start ", serviceName) } } - for serviceName, service := range s.outbounds { - if lateService, isLateService := service.(adapter.PostStarter); isLateService { - s.logger.Trace("post-starting ", service) - err := lateService.PostStart() + for _, outbound := range s.outbounds { + if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound { + s.logger.Trace("post-starting outbound/", outbound.Tag()) + err := lateOutbound.PostStart() if err != nil { - return E.Cause(err, "post-start ", serviceName) + return E.Cause(err, "post-start outbound/", outbound.Tag()) } } } - return nil + s.logger.Trace("post-starting router") + return s.router.PostStart() } func (s *Box) Close() error { diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index e9c8f0f3..763d3801 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -83,7 +83,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) var result map[string]uint16 if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup { - result, err = urlTestGroup.URLTest(ctx, url) + result, err = urlTestGroup.URLTest(ctx) } else { outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound { itOutbound, _ := server.router.Outbound(it) diff --git a/outbound/urltest.go b/outbound/urltest.go index 22578571..3b14d95b 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -3,7 +3,6 @@ package outbound import ( "context" "net" - "sort" "sync" "time" @@ -38,7 +37,6 @@ type URLTest struct { tolerance uint16 group *URLTestGroup interruptExternalConnections bool - started bool } func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { @@ -84,8 +82,7 @@ func (s *URLTest) Start() error { } func (s *URLTest) PostStart() error { - s.started = true - go s.CheckOutbounds() + s.group.PostStart() return nil } @@ -103,8 +100,8 @@ func (s *URLTest) All() []string { return s.tags } -func (s *URLTest) URLTest(ctx context.Context, link string) (map[string]uint16, error) { - return s.group.URLTest(ctx, link) +func (s *URLTest) URLTest(ctx context.Context) (map[string]uint16, error) { + return s.group.URLTest(ctx) } func (s *URLTest) CheckOutbounds() { @@ -112,9 +109,7 @@ func (s *URLTest) CheckOutbounds() { } func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - if s.started { - s.group.Start() - } + s.group.Touch() outbound := s.group.Select(network) conn, err := outbound.DialContext(ctx, network, destination) if err == nil { @@ -126,9 +121,7 @@ func (s *URLTest) DialContext(ctx context.Context, network string, destination M } func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - if s.started { - s.group.Start() - } + s.group.Touch() outbound := s.group.Select(N.NetworkUDP) conn, err := outbound.ListenPacket(ctx, destination) if err == nil { @@ -170,9 +163,11 @@ type URLTestGroup struct { interruptGroup *interrupt.Group interruptExternalConnections bool - access sync.Mutex - ticker *time.Ticker - close chan struct{} + access sync.Mutex + ticker *time.Ticker + close chan struct{} + started bool + lastActive atomic.TypedValue[time.Time] } func NewURLTestGroup( @@ -214,8 +209,18 @@ func NewURLTestGroup( } } -func (g *URLTestGroup) Start() { +func (g *URLTestGroup) PostStart() { + g.started = true + g.lastActive.Store(time.Now()) + go g.CheckOutbounds(false) +} + +func (g *URLTestGroup) Touch() { + if !g.started { + return + } if g.ticker != nil { + g.lastActive.Store(time.Now()) return } g.access.Lock() @@ -266,51 +271,30 @@ func (g *URLTestGroup) Select(network string) adapter.Outbound { return minOutbound } -func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound { - outbounds := make([]adapter.Outbound, 0, len(g.outbounds)-1) - for _, detour := range g.outbounds { - if detour != used { - outbounds = append(outbounds, detour) - } - } - sort.SliceStable(outbounds, func(i, j int) bool { - oi := outbounds[i] - oj := outbounds[j] - hi := g.history.LoadURLTestHistory(RealTag(oi)) - if hi == nil { - return false - } - hj := g.history.LoadURLTestHistory(RealTag(oj)) - if hj == nil { - return false - } - return hi.Delay < hj.Delay - }) - return outbounds -} - func (g *URLTestGroup) loopCheck() { - go g.CheckOutbounds(true) + if time.Now().Sub(g.lastActive.Load()) > g.interval { + g.CheckOutbounds(false) + } for { - g.pauseManager.WaitActive() select { case <-g.close: return case <-g.ticker.C: - g.CheckOutbounds(false) } + g.pauseManager.WaitActive() + g.CheckOutbounds(false) } } func (g *URLTestGroup) CheckOutbounds(force bool) { - _, _ = g.urlTest(g.ctx, g.link, force) + _, _ = g.urlTest(g.ctx, force) } -func (g *URLTestGroup) URLTest(ctx context.Context, link string) (map[string]uint16, error) { - return g.urlTest(ctx, link, false) +func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) { + return g.urlTest(ctx, false) } -func (g *URLTestGroup) urlTest(ctx context.Context, link string, force bool) (map[string]uint16, error) { +func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) { result := make(map[string]uint16) if g.checking.Swap(true) { return result, nil @@ -337,7 +321,7 @@ func (g *URLTestGroup) urlTest(ctx context.Context, link string, force bool) (ma b.Go(realTag, func() (any, error) { ctx, cancel := context.WithTimeout(context.Background(), C.TCPTimeout) defer cancel() - t, err := urltest.URLTest(ctx, link, p) + t, err := urltest.URLTest(ctx, g.link, p) if err != nil { g.logger.Debug("outbound ", tag, " unavailable: ", err) g.history.DeleteURLTestHistory(realTag) diff --git a/route/router.go b/route/router.go index 972c4ec0..b5f0bbbf 100644 --- a/route/router.go +++ b/route/router.go @@ -88,6 +88,7 @@ type Router struct { platformInterface platform.Interface needWIFIState bool wifiState adapter.WIFIState + started bool } func NewRouter( @@ -571,6 +572,11 @@ func (r *Router) Close() error { return err } +func (r *Router) PostStart() error { + r.started = true + return nil +} + func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { outbound, loaded := r.outboundByTag[tag] return outbound, loaded @@ -1015,8 +1021,11 @@ func (r *Router) notifyNetworkUpdate(event int) { } } - r.ResetNetwork() - return + if !r.started { + return + } + + _ = r.ResetNetwork() } func (r *Router) ResetNetwork() error {