Change: proxy gruop strategy improvement
This commit is contained in:
parent
bd4302e096
commit
2334bafe68
7 changed files with 198 additions and 63 deletions
20
adapters/outboundgroup/common.go
Normal file
20
adapters/outboundgroup/common.go
Normal 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
|
||||||
|
}
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
fastSingle *singledo.Single
|
||||||
providers []provider.ProxyProvider
|
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 c, err
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,30 +40,15 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *URLTest) SupportUDP() bool {
|
|
||||||
return u.fast.SupportUDP()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
|
||||||
var all []string
|
|
||||||
for _, proxy := range u.proxies() {
|
|
||||||
all = append(all, proxy.Name())
|
|
||||||
}
|
|
||||||
return json.Marshal(map[string]interface{}{
|
|
||||||
"type": u.Type().String(),
|
|
||||||
"now": u.Now(),
|
|
||||||
"all": all,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return elm.([]C.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *URLTest) fallback() {
|
func (u *URLTest) fast() C.Proxy {
|
||||||
|
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
|
||||||
proxies := u.proxies()
|
proxies := u.proxies()
|
||||||
fast := proxies[0]
|
fast := proxies[0]
|
||||||
min := fast.LastDelay()
|
min := fast.LastDelay()
|
||||||
|
@ -79,15 +63,33 @@ func (u *URLTest) fallback() {
|
||||||
min = delay
|
min = delay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u.fast = fast
|
return fast, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return elm.(C.Proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *URLTest) SupportUDP() bool {
|
||||||
|
return u.fast().SupportUDP()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||||
|
var all []string
|
||||||
|
for _, proxy := range u.proxies() {
|
||||||
|
all = append(all, proxy.Name())
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"type": u.Type().String(),
|
||||||
|
"now": u.Now(),
|
||||||
|
"all": all,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
||||||
|
fastSingle: singledo.NewSingle(time.Second * 10),
|
||||||
providers: providers,
|
providers: providers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
54
common/singledo/singledo.go
Normal file
54
common/singledo/singledo.go
Normal 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)}
|
||||||
|
}
|
52
common/singledo/singledo_test.go
Normal file
52
common/singledo/singledo_test.go
Normal 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)
|
||||||
|
}
|
Loading…
Reference in a new issue