mirror of https://github.com/Dreamacro/clash.git
Feature: add `lazy` for proxy group and provider
This commit is contained in:
parent
4735f61fd1
commit
0402878daa
|
@ -11,10 +11,14 @@ const (
|
|||
defaultGetProxiesDuration = time.Second * 5
|
||||
)
|
||||
|
||||
func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy {
|
||||
func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy {
|
||||
proxies := []C.Proxy{}
|
||||
for _, provider := range providers {
|
||||
proxies = append(proxies, provider.Proxies()...)
|
||||
if touch {
|
||||
proxies = append(proxies, provider.ProxiesWithTouch()...)
|
||||
} else {
|
||||
proxies = append(proxies, provider.Proxies()...)
|
||||
}
|
||||
}
|
||||
return proxies
|
||||
}
|
||||
|
|
|
@ -18,12 +18,12 @@ type Fallback struct {
|
|||
}
|
||||
|
||||
func (f *Fallback) Now() string {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(false)
|
||||
return proxy.Name()
|
||||
}
|
||||
|
||||
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(true)
|
||||
c, err := proxy.DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
c.AppendToChains(f)
|
||||
|
@ -32,7 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
|||
}
|
||||
|
||||
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(true)
|
||||
pc, err := proxy.DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(f)
|
||||
|
@ -45,13 +45,13 @@ func (f *Fallback) SupportUDP() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(false)
|
||||
return proxy.SupportUDP()
|
||||
}
|
||||
|
||||
func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range f.proxies() {
|
||||
for _, proxy := range f.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
|
@ -62,27 +62,27 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxy := f.findAliveProxy()
|
||||
proxy := f.findAliveProxy(true)
|
||||
return proxy
|
||||
}
|
||||
|
||||
func (f *Fallback) proxies() []C.Proxy {
|
||||
func (f *Fallback) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := f.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(f.providers), nil
|
||||
return getProvidersProxies(f.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (f *Fallback) findAliveProxy() C.Proxy {
|
||||
proxies := f.proxies()
|
||||
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||
proxies := f.proxies(touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Alive() {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
return f.proxies()[0]
|
||||
return proxies[0]
|
||||
}
|
||||
|
||||
func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
|
||||
|
|
|
@ -131,13 +131,13 @@ func strategyConsistentHashing() strategyFn {
|
|||
}
|
||||
|
||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
proxies := lb.proxies()
|
||||
proxies := lb.proxies(true)
|
||||
return lb.strategyFn(proxies, metadata)
|
||||
}
|
||||
|
||||
func (lb *LoadBalance) proxies() []C.Proxy {
|
||||
func (lb *LoadBalance) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := lb.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(lb.providers), nil
|
||||
return getProvidersProxies(lb.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
|
@ -145,7 +145,7 @@ func (lb *LoadBalance) proxies() []C.Proxy {
|
|||
|
||||
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range lb.proxies() {
|
||||
for _, proxy := range lb.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
|
|
|
@ -24,13 +24,16 @@ type GroupCommonOption struct {
|
|||
Use []string `group:"use,omitempty"`
|
||||
URL string `group:"url,omitempty"`
|
||||
Interval int `group:"interval,omitempty"`
|
||||
Lazy bool `group:"lazy,omitempty"`
|
||||
DisableUDP bool `group:"disable-udp,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
|
||||
|
||||
groupOption := &GroupCommonOption{}
|
||||
groupOption := &GroupCommonOption{
|
||||
Lazy: true,
|
||||
}
|
||||
if err := decoder.Decode(config, groupOption); err != nil {
|
||||
return nil, errFormat
|
||||
}
|
||||
|
@ -55,7 +58,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||
|
||||
// if Use not empty, drop health check options
|
||||
if len(groupOption.Use) != 0 {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -69,7 +72,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||
|
||||
// select don't need health check
|
||||
if groupOption.Type == "select" || groupOption.Type == "relay" {
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -82,7 +85,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy,
|
|||
return nil, errMissHealthCheck
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy)
|
||||
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -20,7 +20,7 @@ type Relay struct {
|
|||
}
|
||||
|
||||
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
proxies := r.proxies(metadata)
|
||||
proxies := r.proxies(metadata, true)
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("proxy does not exist")
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,
|
|||
|
||||
func (r *Relay) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range r.rawProxies() {
|
||||
for _, proxy := range r.rawProxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
|
@ -67,16 +67,16 @@ func (r *Relay) MarshalJSON() ([]byte, error) {
|
|||
})
|
||||
}
|
||||
|
||||
func (r *Relay) rawProxies() []C.Proxy {
|
||||
func (r *Relay) rawProxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := r.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(r.providers), nil
|
||||
return getProvidersProxies(r.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy {
|
||||
proxies := r.rawProxies()
|
||||
func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
|
||||
proxies := r.rawProxies(touch)
|
||||
|
||||
for n, proxy := range proxies {
|
||||
subproxy := proxy.Unwrap(metadata)
|
||||
|
|
|
@ -20,7 +20,7 @@ type Selector struct {
|
|||
}
|
||||
|
||||
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
|
||||
c, err := s.selectedProxy().DialContext(ctx, metadata)
|
||||
c, err := s.selectedProxy(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
c.AppendToChains(s)
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con
|
|||
}
|
||||
|
||||
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := s.selectedProxy().DialUDP(metadata)
|
||||
pc, err := s.selectedProxy(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(s)
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ func (s *Selector) SupportUDP() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
return s.selectedProxy().SupportUDP()
|
||||
return s.selectedProxy(false).SupportUDP()
|
||||
}
|
||||
|
||||
func (s *Selector) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range getProvidersProxies(s.providers) {
|
||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,11 @@ func (s *Selector) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (s *Selector) Now() string {
|
||||
return s.selectedProxy().Name()
|
||||
return s.selectedProxy(false).Name()
|
||||
}
|
||||
|
||||
func (s *Selector) Set(name string) error {
|
||||
for _, proxy := range getProvidersProxies(s.providers) {
|
||||
for _, proxy := range getProvidersProxies(s.providers, false) {
|
||||
if proxy.Name() == name {
|
||||
s.selected = name
|
||||
s.single.Reset()
|
||||
|
@ -73,12 +73,12 @@ func (s *Selector) Set(name string) error {
|
|||
}
|
||||
|
||||
func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return s.selectedProxy()
|
||||
return s.selectedProxy(true)
|
||||
}
|
||||
|
||||
func (s *Selector) selectedProxy() C.Proxy {
|
||||
func (s *Selector) selectedProxy(touch bool) C.Proxy {
|
||||
elm, _, _ := s.single.Do(func() (interface{}, error) {
|
||||
proxies := getProvidersProxies(s.providers)
|
||||
proxies := getProvidersProxies(s.providers, touch)
|
||||
for _, proxy := range proxies {
|
||||
if proxy.Name() == s.selected {
|
||||
return proxy, nil
|
||||
|
|
|
@ -30,11 +30,11 @@ type URLTest struct {
|
|||
}
|
||||
|
||||
func (u *URLTest) Now() string {
|
||||
return u.fast().Name()
|
||||
return u.fast(false).Name()
|
||||
}
|
||||
|
||||
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
|
||||
c, err = u.fast().DialContext(ctx, metadata)
|
||||
c, err = u.fast(true).DialContext(ctx, metadata)
|
||||
if err == nil {
|
||||
c.AppendToChains(u)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co
|
|||
}
|
||||
|
||||
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
||||
pc, err := u.fast().DialUDP(metadata)
|
||||
pc, err := u.fast(true).DialUDP(metadata)
|
||||
if err == nil {
|
||||
pc.AppendToChains(u)
|
||||
}
|
||||
|
@ -50,20 +50,20 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
|
|||
}
|
||||
|
||||
func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy {
|
||||
return u.fast()
|
||||
return u.fast(true)
|
||||
}
|
||||
|
||||
func (u *URLTest) proxies() []C.Proxy {
|
||||
func (u *URLTest) proxies(touch bool) []C.Proxy {
|
||||
elm, _, _ := u.single.Do(func() (interface{}, error) {
|
||||
return getProvidersProxies(u.providers), nil
|
||||
return getProvidersProxies(u.providers, touch), nil
|
||||
})
|
||||
|
||||
return elm.([]C.Proxy)
|
||||
}
|
||||
|
||||
func (u *URLTest) fast() C.Proxy {
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
|
||||
proxies := u.proxies()
|
||||
proxies := u.proxies(touch)
|
||||
fast := proxies[0]
|
||||
min := fast.LastDelay()
|
||||
for _, proxy := range proxies[1:] {
|
||||
|
@ -94,12 +94,12 @@ func (u *URLTest) SupportUDP() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
return u.fast().SupportUDP()
|
||||
return u.fast(false).SupportUDP()
|
||||
}
|
||||
|
||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
var all []string
|
||||
for _, proxy := range u.proxies() {
|
||||
for _, proxy := range u.proxies(false) {
|
||||
all = append(all, proxy.Name())
|
||||
}
|
||||
return json.Marshal(map[string]interface{}{
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"time"
|
||||
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -17,10 +19,12 @@ type HealthCheckOption struct {
|
|||
}
|
||||
|
||||
type HealthCheck struct {
|
||||
url string
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
done chan struct{}
|
||||
url string
|
||||
proxies []C.Proxy
|
||||
interval uint
|
||||
lazy bool
|
||||
lastTouch *atomic.Int64
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
|
@ -30,7 +34,10 @@ func (hc *HealthCheck) process() {
|
|||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
hc.check()
|
||||
now := time.Now().Unix()
|
||||
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
|
||||
hc.check()
|
||||
}
|
||||
case <-hc.done:
|
||||
ticker.Stop()
|
||||
return
|
||||
|
@ -46,6 +53,10 @@ func (hc *HealthCheck) auto() bool {
|
|||
return hc.interval != 0
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||
for _, proxy := range hc.proxies {
|
||||
|
@ -60,11 +71,13 @@ func (hc *HealthCheck) close() {
|
|||
hc.done <- struct{}{}
|
||||
}
|
||||
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck {
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck {
|
||||
return &HealthCheck{
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
interval: interval,
|
||||
done: make(chan struct{}, 1),
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
interval: interval,
|
||||
lazy: lazy,
|
||||
lastTouch: atomic.NewInt64(0),
|
||||
done: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ type healthCheckSchema struct {
|
|||
Enable bool `provider:"enable"`
|
||||
URL string `provider:"url"`
|
||||
Interval int `provider:"interval"`
|
||||
Lazy bool `provider:"lazy,omitempty"`
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
|
@ -30,7 +31,11 @@ type proxyProviderSchema struct {
|
|||
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
|
||||
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
|
||||
|
||||
schema := &proxyProviderSchema{}
|
||||
schema := &proxyProviderSchema{
|
||||
HealthCheck: healthCheckSchema{
|
||||
Lazy: true,
|
||||
},
|
||||
}
|
||||
if err := decoder.Decode(mapping, schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -39,7 +44,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi
|
|||
if schema.HealthCheck.Enable {
|
||||
hcInterval = uint(schema.HealthCheck.Interval)
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy)
|
||||
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@ type Provider interface {
|
|||
type ProxyProvider interface {
|
||||
Provider
|
||||
Proxies() []C.Proxy
|
||||
// ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies.
|
||||
// Commonly used in Dial and DialUDP
|
||||
ProxiesWithTouch() []C.Proxy
|
||||
HealthCheck()
|
||||
}
|
||||
|
||||
|
@ -112,6 +115,11 @@ func (pp *proxySetProvider) Proxies() []C.Proxy {
|
|||
return pp.proxies
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy {
|
||||
pp.healthCheck.touch()
|
||||
return pp.Proxies()
|
||||
}
|
||||
|
||||
func proxiesParse(buf []byte) (interface{}, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
|
@ -223,6 +231,11 @@ func (cp *compatibleProvider) Proxies() []C.Proxy {
|
|||
return cp.proxies
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy {
|
||||
cp.healthCheck.touch()
|
||||
return cp.Proxies()
|
||||
}
|
||||
|
||||
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||
pd.healthCheck.close()
|
||||
}
|
||||
|
|
|
@ -345,7 +345,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
|||
for _, v := range proxyList {
|
||||
ps = append(ps, proxies[v])
|
||||
}
|
||||
hc := provider.NewHealthCheck(ps, "", 0)
|
||||
hc := provider.NewHealthCheck(ps, "", 0, true)
|
||||
pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc)
|
||||
providersMap[provider.ReservedName] = pd
|
||||
|
||||
|
|
Loading…
Reference in New Issue