@@ -18,7 +18,6 @@ import (
1818 "github.com/sagernet/sing/common/batch"
1919 "github.com/sagernet/sing/common/metadata"
2020 N "github.com/sagernet/sing/common/network"
21- "github.com/sagernet/sing/common/x/list"
2221 "github.com/sagernet/sing/service"
2322 "github.com/sagernet/sing/service/pause"
2423 "go.opentelemetry.io/otel"
@@ -74,16 +73,9 @@ func NewMutableURLTest(ctx context.Context, _ A.Router, logger log.ContextLogger
7473 outboundMgr : outboundMgr ,
7574 connMgr : service.FromContext [A.ConnectionManager ](ctx ),
7675 logger : logger ,
77- group : & urlTestGroup {
78- ctx : ctx ,
79- outboundMgr : outboundMgr ,
80- logger : logger ,
81- tags : options .Outbounds ,
82- link : options .URL ,
83- interval : interval ,
84- idleTimeout : idleTimeout ,
85- tolerance : options .Tolerance ,
86- },
76+ group : newURLTestGroup (
77+ ctx , outboundMgr , logger , options .Outbounds , options .URL , interval , idleTimeout , options .Tolerance ,
78+ ),
8779 }
8880 return outbound , nil
8981}
@@ -216,25 +208,47 @@ type urlTestGroup struct {
216208 ctx context.Context
217209 outboundMgr A.OutboundManager
218210 pauseMgr pause.Manager
219- pauseCallback * list.Element [pause.Callback ]
220211 logger log.Logger
221212 tags []string
222213 outbounds isync.TypedMap [string , A.Outbound ]
223- link string
214+ url string
224215 interval time.Duration
225216 tolerance uint16
226217 idleTimeout time.Duration
227218 history * urltest.HistoryStorage
228- checking atomic.Bool
229219 selectedOutboundTCP atomic.TypedValue [A.Outbound ]
230220 selectedOutboundUDP atomic.TypedValue [A.Outbound ]
231221 access sync.Mutex
232- running atomic.Bool
233- ticker * time.Ticker
222+ checking atomic.Bool
223+ started bool
224+ isAlive bool
234225 idleTimer * time.Timer
235226 lastActive atomic.TypedValue [time.Time ]
236- stop chan struct {}
237- started bool
227+ pauseC chan struct {}
228+ cancel context.CancelFunc
229+ }
230+
231+ func newURLTestGroup (
232+ ctx context.Context ,
233+ outboundMgr A.OutboundManager ,
234+ logger log.ContextLogger ,
235+ tags []string ,
236+ link string ,
237+ interval , idleTimeout time.Duration ,
238+ tolerance uint16 ,
239+ ) * urlTestGroup {
240+ ctx , cancel := context .WithCancel (ctx )
241+ return & urlTestGroup {
242+ ctx : ctx ,
243+ outboundMgr : outboundMgr ,
244+ logger : logger ,
245+ tags : tags ,
246+ url : link ,
247+ interval : interval ,
248+ idleTimeout : idleTimeout ,
249+ tolerance : tolerance ,
250+ cancel : cancel ,
251+ }
238252}
239253
240254func (g * urlTestGroup ) Start () error {
@@ -262,7 +276,6 @@ func (g *urlTestGroup) Start() error {
262276 g .history = urltest .NewHistoryStorage ()
263277 }
264278 g .pauseMgr = service.FromContext [pause.Manager ](g .ctx )
265- g .stop = make (chan struct {}, 1 )
266279 return nil
267280}
268281
@@ -275,23 +288,19 @@ func (g *urlTestGroup) PostStart() {
275288}
276289
277290func (g * urlTestGroup ) Close () error {
278- g .access .Lock ()
279- defer g .access .Unlock ()
280291 if g .isClosed () {
281292 return nil
282293 }
283- if g .ticker != nil {
284- g .ticker .Stop ()
285- g .idleTimer .Stop ()
286- g .pauseMgr .UnregisterCallback (g .pauseCallback )
287- }
288- close (g .stop )
294+ g .access .Lock ()
295+ defer g .access .Unlock ()
296+ g .cancel ()
297+ close (g .pauseC )
289298 return nil
290299}
291300
292301func (g * urlTestGroup ) isClosed () bool {
293302 select {
294- case <- g .stop :
303+ case <- g .ctx . Done () :
295304 return true
296305 default :
297306 return false
@@ -343,9 +352,9 @@ func (g *urlTestGroup) Remove(tags []string) (n int, err error) {
343352 for tag := range g .outbounds .Iter () {
344353 g .tags = append (g .tags , tag )
345354 }
346- if len (g .tags ) == 0 && g .running . Load () {
355+ if len (g .tags ) == 0 && g .isAlive {
347356 select {
348- case g .stop <- struct {}{}:
357+ case g .pauseC <- struct {}{}:
349358 default :
350359 }
351360 }
@@ -359,39 +368,46 @@ func (g *urlTestGroup) keepAlive() {
359368 if ! g .started || len (g .tags ) == 0 {
360369 return
361370 }
362- if ! g . running . CompareAndSwap ( false , true ) {
371+ if g . isAlive {
363372 g .lastActive .Store (time .Now ())
364373 g .idleTimer .Reset (g .idleTimeout )
365374 return
366375 }
367- g .ticker = time . NewTicker ( g . interval )
376+ g .pauseC = make ( chan struct {}, 1 )
368377 go g .checkLoop ()
369- g .pauseCallback = pause .RegisterTicker (g .pauseMgr , g .ticker , g .interval , nil )
370378}
371379
372380func (g * urlTestGroup ) checkLoop () {
373381 if time .Since (g .lastActive .Load ()) > g .interval {
374382 g .lastActive .Store (time .Now ())
375383 g .CheckOutbounds (false )
376384 }
385+ g .access .Lock ()
386+ ctx , cancel := context .WithCancel (g .ctx )
387+ ticker := time .NewTicker (g .interval )
388+ pauseCallback := pause .RegisterTicker (g .pauseMgr , ticker , g .interval , nil )
377389 g .idleTimer = time .NewTimer (g .idleTimeout )
378- loop:
390+ g .isAlive = true
391+ g .access .Unlock ()
392+
393+ defer func () {
394+ cancel ()
395+ g .access .Lock ()
396+ g .pauseMgr .UnregisterCallback (pauseCallback )
397+ g .idleTimer .Stop ()
398+ g .isAlive = false
399+ g .access .Unlock ()
400+ }()
379401 for {
380402 select {
381- case <- g .stop :
382- break loop
383- case <- g .ticker .C :
384- g .CheckOutbounds (false )
403+ case <- g .pauseC :
404+ return
385405 case <- g .idleTimer .C :
386- break loop
406+ return
407+ case <- ticker .C :
408+ go g .urlTest (ctx , false )
387409 }
388410 }
389- g .access .Lock ()
390- g .ticker .Stop ()
391- g .running .Store (false )
392- g .pauseMgr .UnregisterCallback (g .pauseCallback )
393- g .pauseCallback = nil
394- g .access .Unlock ()
395411}
396412
397413func (g * urlTestGroup ) CheckOutbounds (force bool ) {
@@ -431,7 +447,7 @@ func (g *urlTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
431447 b .Go (realTag , func () (any , error ) {
432448 testCtx , cancel := context .WithTimeout (g .ctx , C .TCPTimeout )
433449 defer cancel ()
434- t , err := urltest .URLTest (testCtx , g .link , p )
450+ t , err := urltest .URLTest (testCtx , g .url , p )
435451 if err != nil {
436452 g .logger .Debug ("outbound " , tag , " unavailable: " , err )
437453 g .history .DeleteURLTestHistory (realTag )
0 commit comments