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  }