diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index 944976f1..e54755f8 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -55,7 +55,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, // if Use not empty, drop health check options if len(groupOption.Use) != 0 { - pd, err := provider.NewCompatibleProvier(groupName, ps, nil) + hc := provider.NewHealthCheck(ps, "", 0) + pd, err := provider.NewCompatibleProvier(groupName, ps, hc) if err != nil { return nil, err } @@ -64,7 +65,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, } else { // select don't need health check if groupOption.Type == "select" { - pd, err := provider.NewCompatibleProvier(groupName, ps, nil) + hc := provider.NewHealthCheck(ps, "", 0) + pd, err := provider.NewCompatibleProvier(groupName, ps, hc) if err != nil { return nil, err } @@ -76,11 +78,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, return nil, errMissHealthCheck } - healthOption := &provider.HealthCheckOption{ - URL: groupOption.URL, - Interval: uint(groupOption.Interval), - } - pd, err := provider.NewCompatibleProvier(groupName, ps, healthOption) + hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval)) + pd, err := provider.NewCompatibleProvier(groupName, ps, hc) if err != nil { return nil, err } diff --git a/adapters/provider/healthcheck.go b/adapters/provider/healthcheck.go index 00520666..61f343b1 100644 --- a/adapters/provider/healthcheck.go +++ b/adapters/provider/healthcheck.go @@ -16,20 +16,37 @@ type HealthCheckOption struct { Interval uint } -type healthCheck struct { - url string - proxies []C.Proxy - ticker *time.Ticker +type HealthCheck struct { + url string + proxies []C.Proxy + interval uint + done chan struct{} } -func (hc *healthCheck) process() { +func (hc *HealthCheck) process() { + ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) + go hc.check() - for range hc.ticker.C { - hc.check() + for { + select { + case <-ticker.C: + hc.check() + case <-hc.done: + ticker.Stop() + return + } } } -func (hc *healthCheck) check() { +func (hc *HealthCheck) setProxy(proxies []C.Proxy) { + hc.proxies = proxies +} + +func (hc *HealthCheck) auto() bool { + return hc.interval != 0 +} + +func (hc *HealthCheck) check() { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) for _, proxy := range hc.proxies { go proxy.URLTest(ctx, hc.url) @@ -39,15 +56,15 @@ func (hc *healthCheck) check() { cancel() } -func (hc *healthCheck) close() { - hc.ticker.Stop() +func (hc *HealthCheck) close() { + hc.done <- struct{}{} } -func newHealthCheck(proxies []C.Proxy, url string, interval uint) *healthCheck { - ticker := time.NewTicker(time.Duration(interval) * time.Second) - return &healthCheck{ - proxies: proxies, - url: url, - ticker: ticker, +func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck { + return &HealthCheck{ + proxies: proxies, + url: url, + interval: interval, + done: make(chan struct{}, 1), } } diff --git a/adapters/provider/parser.go b/adapters/provider/parser.go index aea22f8d..7428ad14 100644 --- a/adapters/provider/parser.go +++ b/adapters/provider/parser.go @@ -35,13 +35,11 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi return nil, err } - var healthCheckOption *HealthCheckOption + var hcInterval uint = 0 if schema.HealthCheck.Enable { - healthCheckOption = &HealthCheckOption{ - URL: schema.HealthCheck.URL, - Interval: uint(schema.HealthCheck.Interval), - } + hcInterval = uint(schema.HealthCheck.Interval) } + hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval) path := C.Path.Reslove(schema.Path) @@ -56,5 +54,5 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi } interval := time.Duration(uint(schema.Interval)) * time.Second - return NewProxySetProvider(name, interval, vehicle, healthCheckOption), nil + return NewProxySetProvider(name, interval, vehicle, hc), nil } diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index ddecbbf5..7f60aeda 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -7,9 +7,7 @@ import ( "errors" "fmt" "io/ioutil" - "net/url" "os" - "sync" "time" "github.com/Dreamacro/clash/adapters/outbound" @@ -68,17 +66,13 @@ type ProxySchema struct { } type ProxySetProvider struct { - name string - vehicle Vehicle - hash [16]byte - proxies []C.Proxy - healthCheck *healthCheck - healthCheckOption *HealthCheckOption - ticker *time.Ticker - updatedAt *time.Time - - // mux for avoiding creating new goroutines when pulling - mux sync.Mutex + name string + vehicle Vehicle + hash [16]byte + proxies []C.Proxy + healthCheck *HealthCheck + ticker *time.Ticker + updatedAt *time.Time } func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) { @@ -100,11 +94,7 @@ func (pp *ProxySetProvider) Reload() error { } func (pp *ProxySetProvider) HealthCheck() { - pp.mux.Lock() - defer pp.mux.Unlock() - if pp.healthCheck != nil { - pp.healthCheck.check() - } + pp.healthCheck.check() } func (pp *ProxySetProvider) Update() error { @@ -112,12 +102,7 @@ func (pp *ProxySetProvider) Update() error { } func (pp *ProxySetProvider) Destroy() error { - pp.mux.Lock() - defer pp.mux.Unlock() - if pp.healthCheck != nil { - pp.healthCheck.close() - pp.healthCheck = nil - } + pp.healthCheck.close() if pp.ticker != nil { pp.ticker.Stop() @@ -241,35 +226,32 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) { func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) { pp.proxies = proxies - if pp.healthCheckOption != nil { - pp.mux.Lock() - if pp.healthCheck != nil { - pp.healthCheck.close() - } - pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval) - go pp.healthCheck.process() - pp.mux.Unlock() - } + pp.healthCheck.setProxy(proxies) + go pp.healthCheck.check() } -func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, option *HealthCheckOption) *ProxySetProvider { +func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { var ticker *time.Ticker if interval != 0 { ticker = time.NewTicker(interval) } + if hc.auto() { + go hc.process() + } + return &ProxySetProvider{ - name: name, - vehicle: vehicle, - proxies: []C.Proxy{}, - healthCheckOption: option, - ticker: ticker, + name: name, + vehicle: vehicle, + proxies: []C.Proxy{}, + healthCheck: hc, + ticker: ticker, } } type CompatibleProvier struct { name string - healthCheck *healthCheck + healthCheck *HealthCheck proxies []C.Proxy } @@ -291,16 +273,12 @@ func (cp *CompatibleProvier) Reload() error { } func (cp *CompatibleProvier) Destroy() error { - if cp.healthCheck != nil { - cp.healthCheck.close() - } + cp.healthCheck.close() return nil } func (cp *CompatibleProvier) HealthCheck() { - if cp.healthCheck != nil { - cp.healthCheck.check() - } + cp.healthCheck.check() } func (cp *CompatibleProvier) Update() error { @@ -308,9 +286,6 @@ func (cp *CompatibleProvier) Update() error { } func (cp *CompatibleProvier) Initial() error { - if cp.healthCheck != nil { - go cp.healthCheck.process() - } return nil } @@ -326,17 +301,13 @@ func (cp *CompatibleProvier) Proxies() []C.Proxy { return cp.proxies } -func NewCompatibleProvier(name string, proxies []C.Proxy, option *HealthCheckOption) (*CompatibleProvier, error) { +func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) { if len(proxies) == 0 { return nil, errors.New("Provider need one proxy at least") } - var hc *healthCheck - if option != nil { - if _, err := url.Parse(option.URL); err != nil { - return nil, fmt.Errorf("URL format error: %w", err) - } - hc = newHealthCheck(proxies, option.URL, option.Interval) + if hc.auto() { + go hc.process() } return &CompatibleProvier{