Change: proxy gruop strategy improvement

This commit is contained in:
Dreamacro 2019-12-10 15:04:22 +08:00
parent bd4302e096
commit 2334bafe68
7 changed files with 198 additions and 63 deletions

View file

@ -0,0 +1,20 @@
package outboundgroup
import (
"time"
"github.com/Dreamacro/clash/adapters/provider"
C "github.com/Dreamacro/clash/constant"
)
const (
defaultGetProxiesDuration = time.Second * 5
)
func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy {
proxies := []C.Proxy{}
for _, provider := range providers {
proxies = append(proxies, provider.Proxies()...)
}
return proxies
}

View file

@ -7,11 +7,13 @@ import (
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Fallback struct { type Fallback struct {
*outbound.Base *outbound.Base
single *singledo.Single
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
@ -56,29 +58,28 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
} }
func (f *Fallback) proxies() []C.Proxy { func (f *Fallback) proxies() []C.Proxy {
proxies := []C.Proxy{} elm, _, _ := f.single.Do(func() (interface{}, error) {
for _, provider := range f.providers { return getProvidersProxies(f.providers), nil
proxies = append(proxies, provider.Proxies()...) })
}
return proxies return elm.([]C.Proxy)
} }
func (f *Fallback) findAliveProxy() C.Proxy { func (f *Fallback) findAliveProxy() C.Proxy {
for _, provider := range f.providers { proxies := f.proxies()
proxies := provider.Proxies() for _, proxy := range proxies {
for _, proxy := range proxies { if proxy.Alive() {
if proxy.Alive() { return proxy
return proxy
}
} }
} }
return f.providers[0].Proxies()[0] return f.proxies()[0]
} }
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
return &Fallback{ return &Fallback{
Base: outbound.NewBase(name, C.Fallback, false), Base: outbound.NewBase(name, C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
} }
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
@ -15,6 +16,7 @@ import (
type LoadBalance struct { type LoadBalance struct {
*outbound.Base *outbound.Base
single *singledo.Single
maxRetry int maxRetry int
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
@ -98,11 +100,11 @@ func (lb *LoadBalance) SupportUDP() bool {
} }
func (lb *LoadBalance) proxies() []C.Proxy { func (lb *LoadBalance) proxies() []C.Proxy {
proxies := []C.Proxy{} elm, _, _ := lb.single.Do(func() (interface{}, error) {
for _, provider := range lb.providers { return getProvidersProxies(lb.providers), nil
proxies = append(proxies, provider.Proxies()...) })
}
return proxies return elm.([]C.Proxy)
} }
func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
@ -119,6 +121,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
return &LoadBalance{ return &LoadBalance{
Base: outbound.NewBase(name, C.LoadBalance, false), Base: outbound.NewBase(name, C.LoadBalance, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
maxRetry: 3, maxRetry: 3,
providers: providers, providers: providers,
} }

View file

@ -8,11 +8,13 @@ import (
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type Selector struct { type Selector struct {
*outbound.Base *outbound.Base
single *singledo.Single
selected C.Proxy selected C.Proxy
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
@ -66,17 +68,18 @@ func (s *Selector) Set(name string) error {
} }
func (s *Selector) proxies() []C.Proxy { func (s *Selector) proxies() []C.Proxy {
proxies := []C.Proxy{} elm, _, _ := s.single.Do(func() (interface{}, error) {
for _, provider := range s.providers { return getProvidersProxies(s.providers), nil
proxies = append(proxies, provider.Proxies()...) })
}
return proxies return elm.([]C.Proxy)
} }
func NewSelector(name string, providers []provider.ProxyProvider) *Selector { func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0] selected := providers[0].Proxies()[0]
return &Selector{ return &Selector{
Base: outbound.NewBase(name, C.Selector, false), Base: outbound.NewBase(name, C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, providers: providers,
selected: selected, selected: selected,
} }

View file

@ -4,36 +4,35 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net" "net"
"time"
"github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
type URLTest struct { type URLTest struct {
*outbound.Base *outbound.Base
fast C.Proxy single *singledo.Single
providers []provider.ProxyProvider fastSingle *singledo.Single
providers []provider.ProxyProvider
} }
func (u *URLTest) Now() string { func (u *URLTest) Now() string {
return u.fast.Name() return u.fast().Name()
} }
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
for i := 0; i < 3; i++ { c, err = u.fast().DialContext(ctx, metadata)
c, err = u.fast.DialContext(ctx, metadata) if err == nil {
if err == nil { c.AppendToChains(u)
c.AppendToChains(u)
return
}
u.fallback()
} }
return return c, err
} }
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := u.fast.DialUDP(metadata) pc, addr, err := u.fast().DialUDP(metadata)
if err == nil { if err == nil {
pc.AppendToChains(u) pc.AppendToChains(u)
} }
@ -41,15 +40,37 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error)
} }
func (u *URLTest) proxies() []C.Proxy { func (u *URLTest) proxies() []C.Proxy {
proxies := []C.Proxy{} elm, _, _ := u.single.Do(func() (interface{}, error) {
for _, provider := range u.providers { return getProvidersProxies(u.providers), nil
proxies = append(proxies, provider.Proxies()...) })
}
return proxies return elm.([]C.Proxy)
}
func (u *URLTest) fast() C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
proxies := u.proxies()
fast := proxies[0]
min := fast.LastDelay()
for _, proxy := range proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
return fast, nil
})
return elm.(C.Proxy)
} }
func (u *URLTest) SupportUDP() bool { func (u *URLTest) SupportUDP() bool {
return u.fast.SupportUDP() return u.fast().SupportUDP()
} }
func (u *URLTest) MarshalJSON() ([]byte, error) { func (u *URLTest) MarshalJSON() ([]byte, error) {
@ -64,30 +85,11 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
}) })
} }
func (u *URLTest) fallback() {
proxies := u.proxies()
fast := proxies[0]
min := fast.LastDelay()
for _, proxy := range proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
u.fast = fast
}
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
fast := providers[0].Proxies()[0]
return &URLTest{ return &URLTest{
Base: outbound.NewBase(name, C.URLTest, false), Base: outbound.NewBase(name, C.URLTest, false),
fast: fast, single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers, fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers,
} }
} }

View file

@ -0,0 +1,54 @@
package singledo
import (
"sync"
"time"
)
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
type Single struct {
mux sync.Mutex
last int64
wait int64
call *call
result *Result
}
type Result struct {
Val interface{}
Err error
}
func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
s.mux.Lock()
now := time.Now().Unix()
if now < s.last+s.wait {
s.mux.Unlock()
return s.result.Val, s.result.Err, true
}
if call := s.call; call != nil {
s.mux.Unlock()
call.wg.Wait()
return call.val, call.err, true
}
call := &call{}
call.wg.Add(1)
s.call = call
s.mux.Unlock()
call.val, call.err = fn()
s.call = nil
s.result = &Result{call.val, call.err}
s.last = now
return call.val, call.err, false
}
func NewSingle(wait time.Duration) *Single {
return &Single{wait: int64(wait)}
}

View file

@ -0,0 +1,52 @@
package singledo
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestBasic(t *testing.T) {
single := NewSingle(time.Millisecond * 30)
foo := 0
shardCount := 0
call := func() (interface{}, error) {
foo++
return nil, nil
}
var wg sync.WaitGroup
const n = 10
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
_, _, shard := single.Do(call)
if shard {
shardCount++
}
wg.Done()
}()
}
wg.Wait()
assert.Equal(t, 1, foo)
assert.Equal(t, 9, shardCount)
}
func TestTimer(t *testing.T) {
single := NewSingle(time.Millisecond * 30)
foo := 0
call := func() (interface{}, error) {
foo++
return nil, nil
}
single.Do(call)
time.Sleep(10 * time.Millisecond)
_, _, shard := single.Do(call)
assert.Equal(t, 1, foo)
assert.True(t, shard)
}