github.com/yaling888/clash@v1.53.0/adapter/provider/healthcheck.go (about) 1 package provider 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/samber/lo" 8 "go.uber.org/atomic" 9 10 "github.com/yaling888/clash/common/batch" 11 C "github.com/yaling888/clash/constant" 12 ) 13 14 const ( 15 defaultURLTestTimeout = time.Second * 5 16 ) 17 18 type HealthCheck struct { 19 url string 20 proxies []C.Proxy 21 proxiesFn func() []C.Proxy 22 interval *atomic.Duration 23 lazy bool 24 lastTouch *atomic.Int64 25 ticker *time.Ticker 26 done chan struct{} 27 } 28 29 func (hc *HealthCheck) process() { 30 interval := hc.interval.Load() 31 if hc.ticker != nil || interval == 0 { 32 return 33 } 34 35 hc.ticker = time.NewTicker(interval) 36 37 for { 38 select { 39 case <-hc.ticker.C: 40 now := time.Now().UnixNano() 41 if !hc.lazy || now-hc.lastTouch.Load() < int64(interval) { 42 hc.checkAll() 43 } else { // lazy but still need to check not alive proxies 44 notAliveProxies := lo.Filter(hc.getProxies(), func(proxy C.Proxy, _ int) bool { 45 return !proxy.Alive() 46 }) 47 if len(notAliveProxies) != 0 { 48 hc.check(notAliveProxies) 49 } 50 } 51 case <-hc.done: 52 hc.ticker.Stop() 53 hc.ticker = nil 54 return 55 } 56 } 57 } 58 59 func (hc *HealthCheck) setProxy(proxies []C.Proxy) { 60 hc.proxies = proxies 61 } 62 63 func (hc *HealthCheck) setProxyFn(proxiesFn func() []C.Proxy) { 64 hc.proxiesFn = proxiesFn 65 } 66 67 func (hc *HealthCheck) auto() bool { 68 return hc.interval.Load() != 0 69 } 70 71 func (hc *HealthCheck) touch() { 72 hc.lastTouch.Store(time.Now().UnixNano()) 73 } 74 75 func (hc *HealthCheck) getProxies() []C.Proxy { 76 if hc.proxiesFn != nil { 77 return hc.proxiesFn() 78 } 79 return hc.proxies 80 } 81 82 func (hc *HealthCheck) checkAll() { 83 hc.check(hc.getProxies()) 84 } 85 86 func (hc *HealthCheck) check(proxies []C.Proxy) { 87 if len(proxies) == 0 { 88 return 89 } 90 b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) 91 for _, p := range proxies { 92 b.Go(p.Name(), func() (bool, error) { 93 ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) 94 defer cancel() 95 _, _, _ = p.URLTest(ctx, hc.url) 96 return false, nil 97 }) 98 } 99 b.Wait() 100 } 101 102 func (hc *HealthCheck) close() { 103 if hc.ticker != nil { 104 hc.done <- struct{}{} 105 } 106 hc.interval.Store(0) 107 hc.proxiesFn = nil 108 hc.proxies = nil 109 } 110 111 func NewHealthCheck(proxies []C.Proxy, url string, interval time.Duration, lazy bool) *HealthCheck { 112 if interval < 0 { 113 interval = 0 114 } 115 return &HealthCheck{ 116 proxies: proxies, 117 url: url, 118 interval: atomic.NewDuration(interval), 119 lazy: lazy, 120 lastTouch: atomic.NewInt64(0), 121 done: make(chan struct{}, 1), 122 } 123 }