From 4fd7d0f7074af971020c06de56424a6019c5e677 Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Sun, 24 Apr 2022 02:07:57 +0800 Subject: [PATCH] Chore: use generics as possible --- adapter/outboundgroup/fallback.go | 8 +- adapter/outboundgroup/loadbalance.go | 8 +- adapter/outboundgroup/relay.go | 8 +- adapter/outboundgroup/selector.go | 8 +- adapter/outboundgroup/urltest.go | 16 +- adapter/provider/healthcheck.go | 8 +- common/batch/batch.go | 36 +-- common/batch/batch_test.go | 26 +- common/cache/lrucache.go | 37 +-- common/cache/lrucache_test.go | 18 +- common/generics/list/list.go | 235 ++++++++++++++++++ common/observable/iterable.go | 2 +- common/observable/observable.go | 26 +- common/observable/observable_test.go | 44 ++-- common/observable/subscriber.go | 18 +- common/picker/picker.go | 22 +- common/picker/picker_test.go | 19 +- common/singledo/singledo.go | 42 ++-- common/singledo/singledo_test.go | 26 +- .../strmatcher/ac_automaton_matcher.go | 6 +- component/iface/iface.go | 6 +- component/pool/pool.go | 60 ++--- component/pool/pool_test.go | 26 +- dns/resolver.go | 6 +- hub/executor/executor.go | 12 +- hub/route/server.go | 12 +- ...y_linux_iptables.go => tproxy_iptables.go} | 0 log/log.go | 8 +- transport/snell/pool.go | 24 +- 29 files changed, 500 insertions(+), 267 deletions(-) create mode 100644 common/generics/list/list.go rename listener/tproxy/{tproxy_linux_iptables.go => tproxy_iptables.go} (100%) diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 2e754133..1a2f88fe 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -18,7 +18,7 @@ type Fallback struct { *outbound.Base disableUDP bool filter string - single *singledo.Single + single *singledo.Single[[]C.Proxy] providers []provider.ProxyProvider failedTimes *atomic.Int32 failedTime *atomic.Int64 @@ -115,11 +115,11 @@ func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { } func (f *Fallback) proxies(touch bool) []C.Proxy { - elm, _, _ := f.single.Do(func() (any, error) { + elm, _, _ := f.single.Do(func() ([]C.Proxy, error) { return getProvidersProxies(f.providers, touch, f.filter), nil }) - return elm.([]C.Proxy) + return elm } func (f *Fallback) findAliveProxy(touch bool) C.Proxy { @@ -141,7 +141,7 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) Interface: option.Interface, RoutingMark: option.RoutingMark, }), - single: singledo.NewSingle(defaultGetProxiesDuration), + single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), providers: providers, disableUDP: option.DisableUDP, filter: option.Filter, diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 7c16f48c..aa22240f 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -22,8 +22,8 @@ type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy type LoadBalance struct { *outbound.Base disableUDP bool - single *singledo.Single filter string + single *singledo.Single[[]C.Proxy] providers []provider.ProxyProvider strategyFn strategyFn } @@ -141,11 +141,11 @@ func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { } func (lb *LoadBalance) proxies(touch bool) []C.Proxy { - elm, _, _ := lb.single.Do(func() (any, error) { + elm, _, _ := lb.single.Do(func() ([]C.Proxy, error) { return getProvidersProxies(lb.providers, touch, lb.filter), nil }) - return elm.([]C.Proxy) + return elm } // MarshalJSON implements C.ProxyAdapter @@ -177,7 +177,7 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide Interface: option.Interface, RoutingMark: option.RoutingMark, }), - single: singledo.NewSingle(defaultGetProxiesDuration), + single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), providers: providers, strategyFn: strategyFn, disableUDP: option.DisableUDP, diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index d9b29637..ffccd9a3 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -14,7 +14,7 @@ import ( type Relay struct { *outbound.Base - single *singledo.Single + single *singledo.Single[[]C.Proxy] providers []provider.ProxyProvider filter string } @@ -80,11 +80,11 @@ func (r *Relay) MarshalJSON() ([]byte, error) { } func (r *Relay) rawProxies(touch bool) []C.Proxy { - elm, _, _ := r.single.Do(func() (any, error) { + elm, _, _ := r.single.Do(func() ([]C.Proxy, error) { return getProvidersProxies(r.providers, touch, r.filter), nil }) - return elm.([]C.Proxy) + return elm } func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { @@ -109,7 +109,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re Interface: option.Interface, RoutingMark: option.RoutingMark, }), - single: singledo.NewSingle(defaultGetProxiesDuration), + single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), providers: providers, filter: option.Filter, } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index d3b58b3f..97447f4d 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -15,7 +15,7 @@ import ( type Selector struct { *outbound.Base disableUDP bool - single *singledo.Single + single *singledo.Single[C.Proxy] selected string filter string providers []provider.ProxyProvider @@ -84,7 +84,7 @@ func (s *Selector) Unwrap(*C.Metadata) C.Proxy { } func (s *Selector) selectedProxy(touch bool) C.Proxy { - elm, _, _ := s.single.Do(func() (any, error) { + elm, _, _ := s.single.Do(func() (C.Proxy, error) { proxies := getProvidersProxies(s.providers, touch, s.filter) for _, proxy := range proxies { if proxy.Name() == s.selected { @@ -95,7 +95,7 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy { return proxies[0], nil }) - return elm.(C.Proxy) + return elm } func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { @@ -106,7 +106,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) Interface: option.Interface, RoutingMark: option.RoutingMark, }), - single: singledo.NewSingle(defaultGetProxiesDuration), + single: singledo.NewSingle[C.Proxy](defaultGetProxiesDuration), providers: providers, selected: "COMPATIBLE", disableUDP: option.DisableUDP, diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 98269e6a..11b4a5ce 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -28,8 +28,8 @@ type URLTest struct { disableUDP bool fastNode C.Proxy filter string - single *singledo.Single - fastSingle *singledo.Single + single *singledo.Single[[]C.Proxy] + fastSingle *singledo.Single[C.Proxy] providers []provider.ProxyProvider failedTimes *atomic.Int32 failedTime *atomic.Int64 @@ -71,15 +71,15 @@ func (u *URLTest) Unwrap(*C.Metadata) C.Proxy { } func (u *URLTest) proxies(touch bool) []C.Proxy { - elm, _, _ := u.single.Do(func() (any, error) { + elm, _, _ := u.single.Do(func() ([]C.Proxy, error) { return getProvidersProxies(u.providers, touch, u.filter), nil }) - return elm.([]C.Proxy) + return elm } func (u *URLTest) fast(touch bool) C.Proxy { - elm, _, _ := u.fastSingle.Do(func() (any, error) { + elm, _, _ := u.fastSingle.Do(func() (C.Proxy, error) { proxies := u.proxies(touch) fast := proxies[0] min := fast.LastDelay() @@ -109,7 +109,7 @@ func (u *URLTest) fast(touch bool) C.Proxy { return u.fastNode, nil }) - return elm.(C.Proxy) + return elm } // SupportUDP implements C.ProxyAdapter @@ -181,8 +181,8 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o Interface: option.Interface, RoutingMark: option.RoutingMark, }), - single: singledo.NewSingle(defaultGetProxiesDuration), - fastSingle: singledo.NewSingle(time.Second * 10), + single: singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration), + fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), providers: providers, disableUDP: option.DisableUDP, filter: option.Filter, diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index bfbaf6b0..430225c4 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -65,14 +65,14 @@ func (hc *HealthCheck) touch() { } func (hc *HealthCheck) check() { - b, _ := batch.New(context.Background(), batch.WithConcurrencyNum(10)) + b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) for _, proxy := range hc.proxies { p := proxy - b.Go(p.Name(), func() (any, error) { + b.Go(p.Name(), func() (bool, error) { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) defer cancel() - p.URLTest(ctx, hc.url) - return nil, nil + _, _ = p.URLTest(ctx, hc.url) + return false, nil }) } b.Wait() diff --git a/common/batch/batch.go b/common/batch/batch.go index f0085215..5a6360e5 100644 --- a/common/batch/batch.go +++ b/common/batch/batch.go @@ -5,10 +5,10 @@ import ( "sync" ) -type Option = func(b *Batch) +type Option[T any] func(b *Batch[T]) -type Result struct { - Value any +type Result[T any] struct { + Value T Err error } @@ -17,8 +17,8 @@ type Error struct { Err error } -func WithConcurrencyNum(n int) Option { - return func(b *Batch) { +func WithConcurrencyNum[T any](n int) Option[T] { + return func(b *Batch[T]) { q := make(chan struct{}, n) for i := 0; i < n; i++ { q <- struct{}{} @@ -28,8 +28,8 @@ func WithConcurrencyNum(n int) Option { } // Batch similar to errgroup, but can control the maximum number of concurrent -type Batch struct { - result map[string]Result +type Batch[T any] struct { + result map[string]Result[T] queue chan struct{} wg sync.WaitGroup mux sync.Mutex @@ -38,7 +38,7 @@ type Batch struct { cancel func() } -func (b *Batch) Go(key string, fn func() (any, error)) { +func (b *Batch[T]) Go(key string, fn func() (T, error)) { b.wg.Add(1) go func() { defer b.wg.Done() @@ -59,14 +59,14 @@ func (b *Batch) Go(key string, fn func() (any, error)) { }) } - ret := Result{value, err} + ret := Result[T]{value, err} b.mux.Lock() defer b.mux.Unlock() b.result[key] = ret }() } -func (b *Batch) Wait() *Error { +func (b *Batch[T]) Wait() *Error { b.wg.Wait() if b.cancel != nil { b.cancel() @@ -74,26 +74,26 @@ func (b *Batch) Wait() *Error { return b.err } -func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) { +func (b *Batch[T]) WaitAndGetResult() (map[string]Result[T], *Error) { err := b.Wait() return b.Result(), err } -func (b *Batch) Result() map[string]Result { +func (b *Batch[T]) Result() map[string]Result[T] { b.mux.Lock() defer b.mux.Unlock() - copy := map[string]Result{} + copyM := map[string]Result[T]{} for k, v := range b.result { - copy[k] = v + copyM[k] = v } - return copy + return copyM } -func New(ctx context.Context, opts ...Option) (*Batch, context.Context) { +func New[T any](ctx context.Context, opts ...Option[T]) (*Batch[T], context.Context) { ctx, cancel := context.WithCancel(ctx) - b := &Batch{ - result: map[string]Result{}, + b := &Batch[T]{ + result: map[string]Result[T]{}, } for _, o := range opts { diff --git a/common/batch/batch_test.go b/common/batch/batch_test.go index 4e44158c..73350fd3 100644 --- a/common/batch/batch_test.go +++ b/common/batch/batch_test.go @@ -11,14 +11,14 @@ import ( ) func TestBatch(t *testing.T) { - b, _ := New(context.Background()) + b, _ := New[string](context.Background()) now := time.Now() - b.Go("foo", func() (any, error) { + b.Go("foo", func() (string, error) { time.Sleep(time.Millisecond * 100) return "foo", nil }) - b.Go("bar", func() (any, error) { + b.Go("bar", func() (string, error) { time.Sleep(time.Millisecond * 150) return "bar", nil }) @@ -32,20 +32,20 @@ func TestBatch(t *testing.T) { for k, v := range result { assert.NoError(t, v.Err) - assert.Equal(t, k, v.Value.(string)) + assert.Equal(t, k, v.Value) } } func TestBatchWithConcurrencyNum(t *testing.T) { - b, _ := New( + b, _ := New[string]( context.Background(), - WithConcurrencyNum(3), + WithConcurrencyNum[string](3), ) now := time.Now() for i := 0; i < 7; i++ { idx := i - b.Go(strconv.Itoa(idx), func() (any, error) { + b.Go(strconv.Itoa(idx), func() (string, error) { time.Sleep(time.Millisecond * 100) return strconv.Itoa(idx), nil }) @@ -57,21 +57,21 @@ func TestBatchWithConcurrencyNum(t *testing.T) { for k, v := range result { assert.NoError(t, v.Err) - assert.Equal(t, k, v.Value.(string)) + assert.Equal(t, k, v.Value) } } func TestBatchContext(t *testing.T) { - b, ctx := New(context.Background()) + b, ctx := New[string](context.Background()) - b.Go("error", func() (any, error) { + b.Go("error", func() (string, error) { time.Sleep(time.Millisecond * 100) - return nil, errors.New("test error") + return "", errors.New("test error") }) - b.Go("ctx", func() (any, error) { + b.Go("ctx", func() (string, error) { <-ctx.Done() - return nil, ctx.Err() + return "", ctx.Err() }) result, err := b.WaitAndGetResult() diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 82eca7f4..5fef9445 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -3,19 +3,20 @@ package cache // Modified by https://github.com/die-net/lrucache import ( - "container/list" "sync" "time" + + "github.com/Dreamacro/clash/common/generics/list" ) // Option is part of Functional Options Pattern type Option[K comparable, V any] func(*LruCache[K, V]) // EvictCallback is used to get a callback when a cache entry is evicted -type EvictCallback = func(key any, value any) +type EvictCallback[K comparable, V any] func(key K, value V) // WithEvict set the evict callback -func WithEvict[K comparable, V any](cb EvictCallback) Option[K, V] { +func WithEvict[K comparable, V any](cb EvictCallback[K, V]) Option[K, V] { return func(l *LruCache[K, V]) { l.onEvict = cb } @@ -57,18 +58,18 @@ type LruCache[K comparable, V any] struct { maxAge int64 maxSize int mu sync.Mutex - cache map[any]*list.Element - lru *list.List // Front is least-recent + cache map[K]*list.Element[*entry[K, V]] + lru *list.List[*entry[K, V]] // Front is least-recent updateAgeOnGet bool staleReturn bool - onEvict EvictCallback + onEvict EvictCallback[K, V] } // NewLRUCache creates an LruCache func NewLRUCache[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { lc := &LruCache[K, V]{ - lru: list.New(), - cache: make(map[any]*list.Element), + lru: list.New[*entry[K, V]](), + cache: make(map[K]*list.Element[*entry[K, V]]), } for _, option := range options { @@ -129,7 +130,7 @@ func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) { if le, ok := c.cache[key]; ok { c.lru.MoveToBack(le) - e := le.Value.(*entry[K, V]) + e := le.Value e.value = value e.expires = expires.Unix() } else { @@ -154,11 +155,11 @@ func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) { n.mu.Lock() defer n.mu.Unlock() - n.lru = list.New() - n.cache = make(map[any]*list.Element) + n.lru = list.New[*entry[K, V]]() + n.cache = make(map[K]*list.Element[*entry[K, V]]) for e := c.lru.Front(); e != nil; e = e.Next() { - elm := e.Value.(*entry[K, V]) + elm := e.Value n.cache[elm.key] = n.lru.PushBack(elm) } } @@ -172,7 +173,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] { return nil } - if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry[K, V]).expires <= time.Now().Unix() { + if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() { c.deleteElement(le) c.maybeDeleteOldest() @@ -180,7 +181,7 @@ func (c *LruCache[K, V]) get(key K) *entry[K, V] { } c.lru.MoveToBack(le) - el := le.Value.(*entry[K, V]) + el := le.Value if c.maxAge > 0 && c.updateAgeOnGet { el.expires = time.Now().Unix() + c.maxAge } @@ -201,15 +202,15 @@ func (c *LruCache[K, V]) Delete(key K) { func (c *LruCache[K, V]) maybeDeleteOldest() { if !c.staleReturn && c.maxAge > 0 { now := time.Now().Unix() - for le := c.lru.Front(); le != nil && le.Value.(*entry[K, V]).expires <= now; le = c.lru.Front() { + for le := c.lru.Front(); le != nil && le.Value.expires <= now; le = c.lru.Front() { c.deleteElement(le) } } } -func (c *LruCache[K, V]) deleteElement(le *list.Element) { +func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) { c.lru.Remove(le) - e := le.Value.(*entry[K, V]) + e := le.Value delete(c.cache, e.key) if c.onEvict != nil { c.onEvict(e.key, e.value) @@ -219,7 +220,7 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element) { func (c *LruCache[K, V]) Clear() error { c.mu.Lock() - c.cache = make(map[any]*list.Element) + c.cache = make(map[K]*list.Element[*entry[K, V]]) c.mu.Unlock() return nil diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go index 487c184e..1a4c68ae 100644 --- a/common/cache/lrucache_test.go +++ b/common/cache/lrucache_test.go @@ -52,18 +52,18 @@ func TestLRUMaxAge(t *testing.T) { // Add one expired entry c.Set("foo", "bar") - c.lru.Back().Value.(*entry[string, string]).expires = now + c.lru.Back().Value.expires = now // Reset c.Set("foo", "bar") - e := c.lru.Back().Value.(*entry[string, string]) + e := c.lru.Back().Value assert.True(t, e.expires >= now) - c.lru.Back().Value.(*entry[string, string]).expires = now + c.lru.Back().Value.expires = now // Set a few and verify expiration times for _, s := range entries { c.Set(s.key, s.value) - e := c.lru.Back().Value.(*entry[string, string]) + e := c.lru.Back().Value assert.True(t, e.expires >= expected && e.expires <= expected+10) } @@ -77,7 +77,7 @@ func TestLRUMaxAge(t *testing.T) { for _, s := range entries { le, ok := c.cache[s.key] if assert.True(t, ok) { - le.Value.(*entry[string, string]).expires = now + le.Value.expires = now } } @@ -95,11 +95,11 @@ func TestLRUpdateOnGet(t *testing.T) { // Add one expired entry c.Set("foo", "bar") - c.lru.Back().Value.(*entry[string, string]).expires = expires + c.lru.Back().Value.expires = expires _, ok := c.Get("foo") assert.True(t, ok) - assert.True(t, c.lru.Back().Value.(*entry[string, string]).expires > expires) + assert.True(t, c.lru.Back().Value.expires > expires) } func TestMaxSize(t *testing.T) { @@ -126,8 +126,8 @@ func TestExist(t *testing.T) { func TestEvict(t *testing.T) { temp := 0 - evict := func(key any, value any) { - temp = key.(int) + value.(int) + evict := func(key int, value int) { + temp = key + value } c := NewLRUCache[int, int](WithEvict[int, int](evict), WithSize[int, int](1)) diff --git a/common/generics/list/list.go b/common/generics/list/list.go new file mode 100644 index 00000000..a06a7c61 --- /dev/null +++ b/common/generics/list/list.go @@ -0,0 +1,235 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package list implements a doubly linked list. +// +// To iterate over a list (where l is a *List): +// for e := l.Front(); e != nil; e = e.Next() { +// // do something with e.Value +// } +// +package list + +// Element is an element of a linked list. +type Element[T any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *Element[T] + + // The list to which this element belongs. + list *List[T] + + // The value stored with this element. + Value T +} + +// Next returns the next list element or nil. +func (e *Element[T]) Next() *Element[T] { + if p := e.next; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// Prev returns the previous list element or nil. +func (e *Element[T]) Prev() *Element[T] { + if p := e.prev; e.list != nil && p != &e.list.root { + return p + } + return nil +} + +// List represents a doubly linked list. +// The zero value for List is an empty list ready to use. +type List[T any] struct { + root Element[T] // sentinel list element, only &root, root.prev, and root.next are used + len int // current list length excluding (this) sentinel element +} + +// Init initializes or clears list l. +func (l *List[T]) Init() *List[T] { + l.root.next = &l.root + l.root.prev = &l.root + l.len = 0 + return l +} + +// New returns an initialized list. +func New[T any]() *List[T] { return new(List[T]).Init() } + +// Len returns the number of elements of list l. +// The complexity is O(1). +func (l *List[T]) Len() int { return l.len } + +// Front returns the first element of list l or nil if the list is empty. +func (l *List[T]) Front() *Element[T] { + if l.len == 0 { + return nil + } + return l.root.next +} + +// Back returns the last element of list l or nil if the list is empty. +func (l *List[T]) Back() *Element[T] { + if l.len == 0 { + return nil + } + return l.root.prev +} + +// lazyInit lazily initializes a zero List value. +func (l *List[T]) lazyInit() { + if l.root.next == nil { + l.Init() + } +} + +// insert inserts e after at, increments l.len, and returns e. +func (l *List[T]) insert(e, at *Element[T]) *Element[T] { + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e + e.list = l + l.len++ + return e +} + +// insertValue is a convenience wrapper for insert(&Element{Value: v}, at). +func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] { + return l.insert(&Element[T]{Value: v}, at) +} + +// remove removes e from its list, decrements l.len +func (l *List[T]) remove(e *Element[T]) { + e.prev.next = e.next + e.next.prev = e.prev + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks + e.list = nil + l.len-- +} + +// move moves e to next to at. +func (l *List[T]) move(e, at *Element[T]) { + if e == at { + return + } + e.prev.next = e.next + e.next.prev = e.prev + + e.prev = at + e.next = at.next + e.prev.next = e + e.next.prev = e +} + +// Remove removes e from l if e is an element of list l. +// It returns the element value e.Value. +// The element must not be nil. +func (l *List[T]) Remove(e *Element[T]) T { + if e.list == l { + // if e.list == l, l must have been initialized when e was inserted + // in l or l == nil (e is a zero Element) and l.remove will crash + l.remove(e) + } + return e.Value +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *List[T]) PushFront(v T) *Element[T] { + l.lazyInit() + return l.insertValue(v, &l.root) +} + +// PushBack inserts a new element e with value v at the back of list l and returns e. +func (l *List[T]) PushBack(v T) *Element[T] { + l.lazyInit() + return l.insertValue(v, l.root.prev) +} + +// InsertBefore inserts a new element e with value v immediately before mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark.prev) +} + +// InsertAfter inserts a new element e with value v immediately after mark and returns e. +// If mark is not an element of l, the list is not modified. +// The mark must not be nil. +func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] { + if mark.list != l { + return nil + } + // see comment in List.Remove about initialization of l + return l.insertValue(v, mark) +} + +// MoveToFront moves element e to the front of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *List[T]) MoveToFront(e *Element[T]) { + if e.list != l || l.root.next == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, &l.root) +} + +// MoveToBack moves element e to the back of list l. +// If e is not an element of l, the list is not modified. +// The element must not be nil. +func (l *List[T]) MoveToBack(e *Element[T]) { + if e.list != l || l.root.prev == e { + return + } + // see comment in List.Remove about initialization of l + l.move(e, l.root.prev) +} + +// MoveBefore moves element e to its new position before mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *List[T]) MoveBefore(e, mark *Element[T]) { + if e.list != l || e == mark || mark.list != l { + return + } + l.move(e, mark.prev) +} + +// MoveAfter moves element e to its new position after mark. +// If e or mark is not an element of l, or e == mark, the list is not modified. +// The element and mark must not be nil. +func (l *List[T]) MoveAfter(e, mark *Element[T]) { + if e.list != l || e == mark || mark.list != l { + return + } + l.move(e, mark) +} + +// PushBackList inserts a copy of another list at the back of list l. +// The lists l and other may be the same. They must not be nil. +func (l *List[T]) PushBackList(other *List[T]) { + l.lazyInit() + for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() { + l.insertValue(e.Value, l.root.prev) + } +} + +// PushFrontList inserts a copy of another list at the front of list l. +// The lists l and other may be the same. They must not be nil. +func (l *List[T]) PushFrontList(other *List[T]) { + l.lazyInit() + for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() { + l.insertValue(e.Value, &l.root) + } +} diff --git a/common/observable/iterable.go b/common/observable/iterable.go index 2ac38b40..c78b49a3 100644 --- a/common/observable/iterable.go +++ b/common/observable/iterable.go @@ -1,3 +1,3 @@ package observable -type Iterable <-chan any +type Iterable[T any] <-chan T diff --git a/common/observable/observable.go b/common/observable/observable.go index 64bd0a0a..62b2e153 100644 --- a/common/observable/observable.go +++ b/common/observable/observable.go @@ -5,14 +5,14 @@ import ( "sync" ) -type Observable struct { - iterable Iterable - listener map[Subscription]*Subscriber +type Observable[T any] struct { + iterable Iterable[T] + listener map[Subscription[T]]*Subscriber[T] mux sync.Mutex done bool } -func (o *Observable) process() { +func (o *Observable[T]) process() { for item := range o.iterable { o.mux.Lock() for _, sub := range o.listener { @@ -23,7 +23,7 @@ func (o *Observable) process() { o.close() } -func (o *Observable) close() { +func (o *Observable[T]) close() { o.mux.Lock() defer o.mux.Unlock() @@ -33,18 +33,18 @@ func (o *Observable) close() { } } -func (o *Observable) Subscribe() (Subscription, error) { +func (o *Observable[T]) Subscribe() (Subscription[T], error) { o.mux.Lock() defer o.mux.Unlock() if o.done { - return nil, errors.New("Observable is closed") + return nil, errors.New("observable is closed") } - subscriber := newSubscriber() + subscriber := newSubscriber[T]() o.listener[subscriber.Out()] = subscriber return subscriber.Out(), nil } -func (o *Observable) UnSubscribe(sub Subscription) { +func (o *Observable[T]) UnSubscribe(sub Subscription[T]) { o.mux.Lock() defer o.mux.Unlock() subscriber, exist := o.listener[sub] @@ -55,10 +55,10 @@ func (o *Observable) UnSubscribe(sub Subscription) { subscriber.Close() } -func NewObservable(any Iterable) *Observable { - observable := &Observable{ - iterable: any, - listener: map[Subscription]*Subscriber{}, +func NewObservable[T any](iter Iterable[T]) *Observable[T] { + observable := &Observable[T]{ + iterable: iter, + listener: map[Subscription[T]]*Subscriber[T]{}, } go observable.process() return observable diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index da3e6d5c..5459e0e2 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -9,8 +9,8 @@ import ( "go.uber.org/atomic" ) -func iterator(item []any) chan any { - ch := make(chan any) +func iterator[T any](item []T) chan T { + ch := make(chan T) go func() { time.Sleep(100 * time.Millisecond) for _, elm := range item { @@ -22,8 +22,8 @@ func iterator(item []any) chan any { } func TestObservable(t *testing.T) { - iter := iterator([]any{1, 2, 3, 4, 5}) - src := NewObservable(iter) + iter := iterator[int]([]int{1, 2, 3, 4, 5}) + src := NewObservable[int](iter) data, err := src.Subscribe() assert.Nil(t, err) count := 0 @@ -34,15 +34,15 @@ func TestObservable(t *testing.T) { } func TestObservable_MultiSubscribe(t *testing.T) { - iter := iterator([]any{1, 2, 3, 4, 5}) - src := NewObservable(iter) + iter := iterator[int]([]int{1, 2, 3, 4, 5}) + src := NewObservable[int](iter) ch1, _ := src.Subscribe() ch2, _ := src.Subscribe() count := atomic.NewInt32(0) var wg sync.WaitGroup wg.Add(2) - waitCh := func(ch <-chan any) { + waitCh := func(ch <-chan int) { for range ch { count.Inc() } @@ -55,8 +55,8 @@ func TestObservable_MultiSubscribe(t *testing.T) { } func TestObservable_UnSubscribe(t *testing.T) { - iter := iterator([]any{1, 2, 3, 4, 5}) - src := NewObservable(iter) + iter := iterator[int]([]int{1, 2, 3, 4, 5}) + src := NewObservable[int](iter) data, err := src.Subscribe() assert.Nil(t, err) src.UnSubscribe(data) @@ -65,8 +65,8 @@ func TestObservable_UnSubscribe(t *testing.T) { } func TestObservable_SubscribeClosedSource(t *testing.T) { - iter := iterator([]any{1}) - src := NewObservable(iter) + iter := iterator[int]([]int{1}) + src := NewObservable[int](iter) data, _ := src.Subscribe() <-data @@ -75,18 +75,18 @@ func TestObservable_SubscribeClosedSource(t *testing.T) { } func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { - sub := Subscription(make(chan any)) - iter := iterator([]any{1}) - src := NewObservable(iter) + sub := Subscription[int](make(chan int)) + iter := iterator[int]([]int{1}) + src := NewObservable[int](iter) src.UnSubscribe(sub) } func TestObservable_SubscribeGoroutineLeak(t *testing.T) { - iter := iterator([]any{1, 2, 3, 4, 5}) - src := NewObservable(iter) + iter := iterator[int]([]int{1, 2, 3, 4, 5}) + src := NewObservable[int](iter) max := 100 - var list []Subscription + var list []Subscription[int] for i := 0; i < max; i++ { ch, _ := src.Subscribe() list = append(list, ch) @@ -94,7 +94,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) { var wg sync.WaitGroup wg.Add(max) - waitCh := func(ch <-chan any) { + waitCh := func(ch <-chan int) { for range ch { } wg.Done() @@ -115,11 +115,11 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) { } func Benchmark_Observable_1000(b *testing.B) { - ch := make(chan any) - o := NewObservable(ch) + ch := make(chan int) + o := NewObservable[int](ch) num := 1000 - subs := []Subscription{} + subs := []Subscription[int]{} for i := 0; i < num; i++ { sub, _ := o.Subscribe() subs = append(subs, sub) @@ -130,7 +130,7 @@ func Benchmark_Observable_1000(b *testing.B) { b.ResetTimer() for _, sub := range subs { - go func(s Subscription) { + go func(s Subscription[int]) { for range s { } wg.Done() diff --git a/common/observable/subscriber.go b/common/observable/subscriber.go index 0d8559bc..b7df4cae 100644 --- a/common/observable/subscriber.go +++ b/common/observable/subscriber.go @@ -4,30 +4,30 @@ import ( "sync" ) -type Subscription <-chan any +type Subscription[T any] <-chan T -type Subscriber struct { - buffer chan any +type Subscriber[T any] struct { + buffer chan T once sync.Once } -func (s *Subscriber) Emit(item any) { +func (s *Subscriber[T]) Emit(item T) { s.buffer <- item } -func (s *Subscriber) Out() Subscription { +func (s *Subscriber[T]) Out() Subscription[T] { return s.buffer } -func (s *Subscriber) Close() { +func (s *Subscriber[T]) Close() { s.once.Do(func() { close(s.buffer) }) } -func newSubscriber() *Subscriber { - sub := &Subscriber{ - buffer: make(chan any, 200), +func newSubscriber[T any]() *Subscriber[T] { + sub := &Subscriber[T]{ + buffer: make(chan T, 200), } return sub } diff --git a/common/picker/picker.go b/common/picker/picker.go index e701268a..97004460 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -9,7 +9,7 @@ import ( // Picker provides synchronization, and Context cancelation // for groups of goroutines working on subtasks of a common task. // Inspired by errGroup -type Picker struct { +type Picker[T any] struct { ctx context.Context cancel func() @@ -17,12 +17,12 @@ type Picker struct { once sync.Once errOnce sync.Once - result any + result T err error } -func newPicker(ctx context.Context, cancel func()) *Picker { - return &Picker{ +func newPicker[T any](ctx context.Context, cancel func()) *Picker[T] { + return &Picker[T]{ ctx: ctx, cancel: cancel, } @@ -30,20 +30,20 @@ func newPicker(ctx context.Context, cancel func()) *Picker { // WithContext returns a new Picker and an associated Context derived from ctx. // and cancel when first element return. -func WithContext(ctx context.Context) (*Picker, context.Context) { +func WithContext[T any](ctx context.Context) (*Picker[T], context.Context) { ctx, cancel := context.WithCancel(ctx) - return newPicker(ctx, cancel), ctx + return newPicker[T](ctx, cancel), ctx } // WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. -func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { +func WithTimeout[T any](ctx context.Context, timeout time.Duration) (*Picker[T], context.Context) { ctx, cancel := context.WithTimeout(ctx, timeout) - return newPicker(ctx, cancel), ctx + return newPicker[T](ctx, cancel), ctx } // Wait blocks until all function calls from the Go method have returned, // then returns the first nil error result (if any) from them. -func (p *Picker) Wait() any { +func (p *Picker[T]) Wait() T { p.wg.Wait() if p.cancel != nil { p.cancel() @@ -52,13 +52,13 @@ func (p *Picker) Wait() any { } // Error return the first error (if all success return nil) -func (p *Picker) Error() error { +func (p *Picker[T]) Error() error { return p.err } // Go calls the given function in a new goroutine. // The first call to return a nil error cancels the group; its result will be returned by Wait. -func (p *Picker) Go(f func() (any, error)) { +func (p *Picker[T]) Go(f func() (T, error)) { p.wg.Add(1) go func() { diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index ca10499d..17b823cb 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -8,33 +8,38 @@ import ( "github.com/stretchr/testify/assert" ) -func sleepAndSend(ctx context.Context, delay int, input any) func() (any, error) { - return func() (any, error) { +func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, error) { + return func() (T, error) { timer := time.NewTimer(time.Millisecond * time.Duration(delay)) select { case <-timer.C: return input, nil case <-ctx.Done(): - return nil, ctx.Err() + return getZero[T](), ctx.Err() } } } func TestPicker_Basic(t *testing.T) { - picker, ctx := WithContext(context.Background()) + picker, ctx := WithContext[int](context.Background()) picker.Go(sleepAndSend(ctx, 30, 2)) picker.Go(sleepAndSend(ctx, 20, 1)) number := picker.Wait() assert.NotNil(t, number) - assert.Equal(t, number.(int), 1) + assert.Equal(t, number, 1) } func TestPicker_Timeout(t *testing.T) { - picker, ctx := WithTimeout(context.Background(), time.Millisecond*5) + picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5) picker.Go(sleepAndSend(ctx, 20, 1)) number := picker.Wait() - assert.Nil(t, number) + assert.Equal(t, number, getZero[int]()) assert.NotNil(t, picker.Error()) } + +func getZero[T any]() T { + var result T + return result +} diff --git a/common/singledo/singledo.go b/common/singledo/singledo.go index f6ff35a9..c741fc82 100644 --- a/common/singledo/singledo.go +++ b/common/singledo/singledo.go @@ -5,28 +5,28 @@ import ( "time" ) -type call struct { +type call[T any] struct { wg sync.WaitGroup - val any + val T err error } -type Single struct { +type Single[T any] struct { mux sync.Mutex last time.Time wait time.Duration - call *call - result *Result + call *call[T] + result *Result[T] } -type Result struct { - Val any +type Result[T any] struct { + Val T Err error } // Do single.Do likes sync.singleFlight //lint:ignore ST1008 it likes sync.singleFlight -func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) { +func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) { s.mux.Lock() now := time.Now() if now.Before(s.last.Add(s.wait)) { @@ -34,31 +34,31 @@ func (s *Single) Do(fn func() (any, error)) (v any, err error, shared bool) { return s.result.Val, s.result.Err, true } - if call := s.call; call != nil { + if callM := s.call; callM != nil { s.mux.Unlock() - call.wg.Wait() - return call.val, call.err, true + callM.wg.Wait() + return callM.val, callM.err, true } - call := &call{} - call.wg.Add(1) - s.call = call + callM := &call[T]{} + callM.wg.Add(1) + s.call = callM s.mux.Unlock() - call.val, call.err = fn() - call.wg.Done() + callM.val, callM.err = fn() + callM.wg.Done() s.mux.Lock() s.call = nil - s.result = &Result{call.val, call.err} + s.result = &Result[T]{callM.val, callM.err} s.last = now s.mux.Unlock() - return call.val, call.err, false + return callM.val, callM.err, false } -func (s *Single) Reset() { +func (s *Single[T]) Reset() { s.last = time.Time{} } -func NewSingle(wait time.Duration) *Single { - return &Single{wait: wait} +func NewSingle[T any](wait time.Duration) *Single[T] { + return &Single[T]{wait: wait} } diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index 71b6ac9f..26e3d37d 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -10,13 +10,13 @@ import ( ) func TestBasic(t *testing.T) { - single := NewSingle(time.Millisecond * 30) + single := NewSingle[int](time.Millisecond * 30) foo := 0 shardCount := atomic.NewInt32(0) - call := func() (any, error) { + call := func() (int, error) { foo++ time.Sleep(time.Millisecond * 5) - return nil, nil + return 0, nil } var wg sync.WaitGroup @@ -38,32 +38,32 @@ func TestBasic(t *testing.T) { } func TestTimer(t *testing.T) { - single := NewSingle(time.Millisecond * 30) + single := NewSingle[int](time.Millisecond * 30) foo := 0 - call := func() (any, error) { + callM := func() (int, error) { foo++ - return nil, nil + return 0, nil } - single.Do(call) + _, _, _ = single.Do(callM) time.Sleep(10 * time.Millisecond) - _, _, shard := single.Do(call) + _, _, shard := single.Do(callM) assert.Equal(t, 1, foo) assert.True(t, shard) } func TestReset(t *testing.T) { - single := NewSingle(time.Millisecond * 30) + single := NewSingle[int](time.Millisecond * 30) foo := 0 - call := func() (any, error) { + callM := func() (int, error) { foo++ - return nil, nil + return 0, nil } - single.Do(call) + _, _, _ = single.Do(callM) single.Reset() - single.Do(call) + _, _, _ = single.Do(callM) assert.Equal(t, 2, foo) } diff --git a/component/geodata/strmatcher/ac_automaton_matcher.go b/component/geodata/strmatcher/ac_automaton_matcher.go index ef0bc5d9..d134c68a 100644 --- a/component/geodata/strmatcher/ac_automaton_matcher.go +++ b/component/geodata/strmatcher/ac_automaton_matcher.go @@ -1,7 +1,7 @@ package strmatcher import ( - "container/list" + "github.com/Dreamacro/clash/common/generics/list" ) const validCharCount = 53 @@ -190,7 +190,7 @@ func (ac *ACAutomaton) Add(domain string, t Type) { } func (ac *ACAutomaton) Build() { - queue := list.New() + queue := list.New[Edge]() for i := 0; i < validCharCount; i++ { if ac.trie[0][i].nextNode != 0 { queue.PushBack(ac.trie[0][i]) @@ -201,7 +201,7 @@ func (ac *ACAutomaton) Build() { if front == nil { break } else { - node := front.Value.(Edge).nextNode + node := front.Value.nextNode queue.Remove(front) for i := 0; i < validCharCount; i++ { if ac.trie[node][i].nextNode != 0 { diff --git a/component/iface/iface.go b/component/iface/iface.go index 637d4876..11c754f8 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -21,10 +21,10 @@ var ( ErrAddrNotFound = errors.New("addr not found") ) -var interfaces = singledo.NewSingle(time.Second * 20) +var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20) func ResolveInterface(name string) (*Interface, error) { - value, err, _ := interfaces.Do(func() (any, error) { + value, err, _ := interfaces.Do(func() (map[string]*Interface, error) { ifaces, err := net.Interfaces() if err != nil { return nil, err @@ -66,7 +66,7 @@ func ResolveInterface(name string) (*Interface, error) { return nil, err } - ifaces := value.(map[string]*Interface) + ifaces := value iface, ok := ifaces[name] if !ok { return nil, ErrIfaceNotFound diff --git a/component/pool/pool.go b/component/pool/pool.go index ef117539..f8173761 100644 --- a/component/pool/pool.go +++ b/component/pool/pool.go @@ -6,55 +6,55 @@ import ( "time" ) -type Factory = func(context.Context) (any, error) +type Factory[T any] func(context.Context) (T, error) -type entry struct { - elm any +type entry[T any] struct { + elm T time time.Time } -type Option func(*pool) +type Option[T any] func(*pool[T]) // WithEvict set the evict callback -func WithEvict(cb func(any)) Option { - return func(p *pool) { +func WithEvict[T any](cb func(T)) Option[T] { + return func(p *pool[T]) { p.evict = cb } } // WithAge defined element max age (millisecond) -func WithAge(maxAge int64) Option { - return func(p *pool) { +func WithAge[T any](maxAge int64) Option[T] { + return func(p *pool[T]) { p.maxAge = maxAge } } // WithSize defined max size of Pool -func WithSize(maxSize int) Option { - return func(p *pool) { - p.ch = make(chan any, maxSize) +func WithSize[T any](maxSize int) Option[T] { + return func(p *pool[T]) { + p.ch = make(chan *entry[T], maxSize) } } // Pool is for GC, see New for detail -type Pool struct { - *pool +type Pool[T any] struct { + *pool[T] } -type pool struct { - ch chan any - factory Factory - evict func(any) +type pool[T any] struct { + ch chan *entry[T] + factory Factory[T] + evict func(T) maxAge int64 } -func (p *pool) GetContext(ctx context.Context) (any, error) { +func (p *pool[T]) GetContext(ctx context.Context) (T, error) { now := time.Now() for { select { case item := <-p.ch: - elm := item.(*entry) - if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge { + elm := item + if p.maxAge != 0 && now.Sub(item.time).Milliseconds() > p.maxAge { if p.evict != nil { p.evict(elm.elm) } @@ -68,12 +68,12 @@ func (p *pool) GetContext(ctx context.Context) (any, error) { } } -func (p *pool) Get() (any, error) { +func (p *pool[T]) Get() (T, error) { return p.GetContext(context.Background()) } -func (p *pool) Put(item any) { - e := &entry{ +func (p *pool[T]) Put(item T) { + e := &entry[T]{ elm: item, time: time.Now(), } @@ -90,17 +90,17 @@ func (p *pool) Put(item any) { } } -func recycle(p *Pool) { +func recycle[T any](p *Pool[T]) { for item := range p.pool.ch { if p.pool.evict != nil { - p.pool.evict(item.(*entry).elm) + p.pool.evict(item.elm) } } } -func New(factory Factory, options ...Option) *Pool { - p := &pool{ - ch: make(chan any, 10), +func New[T any](factory Factory[T], options ...Option[T]) *Pool[T] { + p := &pool[T]{ + ch: make(chan *entry[T], 10), factory: factory, } @@ -108,7 +108,7 @@ func New(factory Factory, options ...Option) *Pool { option(p) } - P := &Pool{p} - runtime.SetFinalizer(P, recycle) + P := &Pool[T]{p} + runtime.SetFinalizer(P, recycle[T]) return P } diff --git a/component/pool/pool_test.go b/component/pool/pool_test.go index 5492f4c8..752aaace 100644 --- a/component/pool/pool_test.go +++ b/component/pool/pool_test.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/assert" ) -func lg() Factory { +func lg() Factory[int] { initial := -1 - return func(context.Context) (any, error) { + return func(context.Context) (int, error) { initial++ return initial, nil } @@ -18,23 +18,23 @@ func lg() Factory { func TestPool_Basic(t *testing.T) { g := lg() - pool := New(g) + pool := New[int](g) elm, _ := pool.Get() - assert.Equal(t, 0, elm.(int)) + assert.Equal(t, 0, elm) pool.Put(elm) elm, _ = pool.Get() - assert.Equal(t, 0, elm.(int)) + assert.Equal(t, 0, elm) elm, _ = pool.Get() - assert.Equal(t, 1, elm.(int)) + assert.Equal(t, 1, elm) } func TestPool_MaxSize(t *testing.T) { g := lg() size := 5 - pool := New(g, WithSize(size)) + pool := New[int](g, WithSize[int](size)) - var items []any + var items []int for i := 0; i < size; i++ { item, _ := pool.Get() @@ -42,7 +42,7 @@ func TestPool_MaxSize(t *testing.T) { } extra, _ := pool.Get() - assert.Equal(t, size, extra.(int)) + assert.Equal(t, size, extra) for _, item := range items { pool.Put(item) @@ -52,22 +52,22 @@ func TestPool_MaxSize(t *testing.T) { for _, item := range items { elm, _ := pool.Get() - assert.Equal(t, item.(int), elm.(int)) + assert.Equal(t, item, elm) } } func TestPool_MaxAge(t *testing.T) { g := lg() - pool := New(g, WithAge(20)) + pool := New[int](g, WithAge[int](20)) elm, _ := pool.Get() pool.Put(elm) elm, _ = pool.Get() - assert.Equal(t, 0, elm.(int)) + assert.Equal(t, 0, elm) pool.Put(elm) time.Sleep(time.Millisecond * 22) elm, _ = pool.Get() - assert.Equal(t, 1, elm.(int)) + assert.Equal(t, 1, elm) } diff --git a/dns/resolver.go b/dns/resolver.go index 369cb9f8..4b4dccc0 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -199,10 +199,10 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M } func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { - fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout) + fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) for _, client := range clients { r := client - fast.Go(func() (any, error) { + fast.Go(func() (*D.Msg, error) { m, err := r.ExchangeContext(ctx, m) if err != nil { return nil, err @@ -222,7 +222,7 @@ func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D. return nil, err } - msg = elm.(*D.Msg) + msg = elm return } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 8f911840..404e7fe4 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,11 +2,9 @@ package executor import ( "fmt" - "net" "net/netip" "os" "runtime" - "strconv" "sync" "github.com/Dreamacro/clash/adapter" @@ -376,13 +374,7 @@ func updateIPTables(cfg *config.Config) { return } - _, dnsPortStr, err := net.SplitHostPort(dnsCfg.Listen) - if err != nil { - err = fmt.Errorf("DNS server must be correct") - return - } - - dnsPort, err := strconv.ParseUint(dnsPortStr, 10, 16) + dnsPort, err := netip.ParseAddrPort(dnsCfg.Listen) if err != nil { err = fmt.Errorf("DNS server must be correct") return @@ -396,7 +388,7 @@ func updateIPTables(cfg *config.Config) { dialer.DefaultRoutingMark.Store(2158) } - err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), uint16(dnsPort)) + err = tproxy.SetTProxyIPTables(inboundInterface, bypass, uint16(tProxyPort), dnsPort.Port()) if err != nil { return } diff --git a/hub/route/server.go b/hub/route/server.go index cad8e49d..9bb15ddd 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -51,14 +51,14 @@ func Start(addr string, secret string) { r := chi.NewRouter() - cors := cors.New(cors.Options{ + corsM := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) - r.Use(cors.Handler) + r.Use(corsM.Handler) r.Group(func(r chi.Router) { r.Use(authentication) @@ -217,14 +217,14 @@ func getLogs(w http.ResponseWriter, r *http.Request) { var err error for elm := range sub { buf.Reset() - log := elm.(*log.Event) - if log.LogLevel < level { + logM := elm + if logM.LogLevel < level { continue } if err := json.NewEncoder(buf).Encode(Log{ - Type: log.Type(), - Payload: log.Payload, + Type: logM.Type(), + Payload: logM.Payload, }); err != nil { break } diff --git a/listener/tproxy/tproxy_linux_iptables.go b/listener/tproxy/tproxy_iptables.go similarity index 100% rename from listener/tproxy/tproxy_linux_iptables.go rename to listener/tproxy/tproxy_iptables.go diff --git a/log/log.go b/log/log.go index 4e5c375b..58e122ef 100644 --- a/log/log.go +++ b/log/log.go @@ -10,8 +10,8 @@ import ( ) var ( - logCh = make(chan any) - source = observable.NewObservable(logCh) + logCh = make(chan *Event) + source = observable.NewObservable[*Event](logCh) level = INFO ) @@ -57,12 +57,12 @@ func Fatalln(format string, v ...any) { log.Fatalf(format, v...) } -func Subscribe() observable.Subscription { +func Subscribe() observable.Subscription[*Event] { sub, _ := source.Subscribe() return sub } -func UnSubscribe(sub observable.Subscription) { +func UnSubscribe(sub observable.Subscription[*Event]) { source.UnSubscribe(sub) } diff --git a/transport/snell/pool.go b/transport/snell/pool.go index 62d21b4e..237baf21 100644 --- a/transport/snell/pool.go +++ b/transport/snell/pool.go @@ -11,7 +11,7 @@ import ( ) type Pool struct { - pool *pool.Pool + pool *pool.Pool[*Snell] } func (p *Pool) Get() (net.Conn, error) { @@ -24,12 +24,12 @@ func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) { return nil, err } - return &PoolConn{elm.(*Snell), p}, nil + return &PoolConn{elm, p}, nil } -func (p *Pool) Put(conn net.Conn) { +func (p *Pool) Put(conn *Snell) { if err := HalfClose(conn); err != nil { - conn.Close() + _ = conn.Close() return } @@ -64,22 +64,22 @@ func (pc *PoolConn) Write(b []byte) (int, error) { func (pc *PoolConn) Close() error { // clash use SetReadDeadline to break bidirectional copy between client and server. // reset it before reuse connection to avoid io timeout error. - pc.Snell.Conn.SetReadDeadline(time.Time{}) + _ = pc.Snell.Conn.SetReadDeadline(time.Time{}) pc.pool.Put(pc.Snell) return nil } func NewPool(factory func(context.Context) (*Snell, error)) *Pool { - p := pool.New( - func(ctx context.Context) (any, error) { + p := pool.New[*Snell]( + func(ctx context.Context) (*Snell, error) { return factory(ctx) }, - pool.WithAge(15000), - pool.WithSize(10), - pool.WithEvict(func(item any) { - item.(*Snell).Close() + pool.WithAge[*Snell](15000), + pool.WithSize[*Snell](10), + pool.WithEvict[*Snell](func(item *Snell) { + _ = item.Close() }), ) - return &Pool{p} + return &Pool{pool: p} }