Compare commits

..
Author SHA1 Message Date
Fangliding 6d3ebc4239 Print trace if close take too long 2026-05-19 22:18:26 +08:00
5 changed files with 40 additions and 68 deletions
+8 -9
View File
@@ -8,7 +8,6 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata/strmatcher" "github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
) )
type DomainMatcher interface { type DomainMatcher interface {
@@ -26,7 +25,7 @@ type DomainMatcherFactory interface {
type MphDomainMatcherFactory struct { type MphDomainMatcherFactory struct {
sync.Mutex sync.Mutex
shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher] shared map[string]strmatcher.MatcherGroup // TODO: cleanup
} }
func buildDomainRulesKey(rules []*DomainRule) string { func buildDomainRulesKey(rules []*DomainRule) string {
@@ -66,7 +65,7 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
if key != "" { if key != "" {
f.Lock() f.Lock()
defer f.Unlock() defer f.Unlock()
if g, ok := f.shared.Load(key); ok { if g := f.shared[key]; g != nil {
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules") errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
return g, nil return g, nil
} }
@@ -103,14 +102,14 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
return nil, err return nil, err
} }
if key != "" { if key != "" {
f.shared.Store(key, g) f.shared[key] = g
} }
return g, nil return g, nil
} }
type CompactDomainMatcherFactory struct { type CompactDomainMatcherFactory struct {
sync.Mutex sync.Mutex
shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher] shared map[string]strmatcher.MatcherSet // TODO: cleanup
} }
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) { func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
@@ -119,7 +118,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
f.Lock() f.Lock()
defer f.Unlock() defer f.Unlock()
if s, ok := f.shared.Load(key); ok { if s := f.shared[key]; s != nil {
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key) errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
return s, nil return s, nil
} }
@@ -139,7 +138,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
} }
s.Add(m) s.Add(m)
} }
f.shared.Store(key, s) f.shared[key] = s
return s, err return s, err
} }
@@ -231,8 +230,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
func newDomainMatcherFactory() DomainMatcherFactory { func newDomainMatcherFactory() DomainMatcherFactory {
switch runtime.GOOS { switch runtime.GOOS {
case "ios", "android": case "ios", "android":
return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()} return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
default: default:
return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()} return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
} }
} }
+3 -5
View File
@@ -7,11 +7,10 @@ import (
"testing" "testing"
"github.com/xtls/xray-core/common/geodata/strmatcher" "github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
) )
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) { func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()} factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
matcher, err := factory.BuildMatcher([]*DomainRule{ matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}}, {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}}, {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
@@ -32,7 +31,7 @@ func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) { func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()} factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
matcher, err := factory.BuildMatcher([]*DomainRule{ matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}}, {Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}}, {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
@@ -51,8 +50,7 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
} }
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) { func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}). matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{
BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}}, {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}}, {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
}) })
+4 -5
View File
@@ -11,7 +11,6 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"go4.org/netipx" "go4.org/netipx"
) )
@@ -807,7 +806,7 @@ func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
type IPSetFactory struct { type IPSetFactory struct {
sync.Mutex sync.Mutex
shared *utils.WeakCacheMap[string, IPSet] shared map[string]*IPSet // TODO: cleanup
} }
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) { func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
@@ -816,7 +815,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
f.Lock() f.Lock()
defer f.Unlock() defer f.Unlock()
if ipset, ok := f.shared.Load(key); ok { if ipset := f.shared[key]; ipset != nil {
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key) errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
return ipset, nil return ipset, nil
} }
@@ -836,7 +835,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
return nil return nil
}) })
if err == nil { if err == nil {
f.shared.Store(key, ipset) f.shared[key] = ipset
} }
return ipset, err return ipset, err
} }
@@ -1019,5 +1018,5 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
} }
func newIPSetFactory() *IPSetFactory { func newIPSetFactory() *IPSetFactory {
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()} return &IPSetFactory{shared: make(map[string]*IPSet)}
} }
-45
View File
@@ -1,45 +0,0 @@
package utils
import (
"runtime"
"sync"
"weak"
)
// WeakCacheMap is a map that holds weak references to values.
// Use for shared expensive objects and automatic cleanup when no longer used.
// This object can be GC and no goroutine is used for cleanup.
type WeakCacheMap[K comparable, V any] struct {
mu sync.Mutex
m map[K]weak.Pointer[V]
}
func NewWeakCacheMap[K comparable, V any]() *WeakCacheMap[K, V] {
return &WeakCacheMap[K, V]{
m: make(map[K]weak.Pointer[V]),
}
}
func (c *WeakCacheMap[K, V]) Load(key K) (value *V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
weakPtr := c.m[key].Value()
if weakPtr != nil {
return weakPtr, true
}
return nil, false
}
func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
c.mu.Lock()
defer c.mu.Unlock()
weakPtr := weak.Make(value)
c.m[key] = weakPtr
runtime.AddCleanup(value, func(struct{}) {
c.mu.Lock()
defer c.mu.Unlock()
if c.m[key] == weakPtr {
delete(c.m, key)
}
}, struct{}{})
}
+22 -1
View File
@@ -91,7 +91,28 @@ func executeRun(cmd *base.Command, args []string) {
fmt.Println("Failed to start:", err) fmt.Println("Failed to start:", err)
os.Exit(-1) os.Exit(-1)
} }
defer server.Close() defer func() {
closeErrCh := make(chan error, 1)
go func() {
closeErrCh <- server.Close()
}()
select {
case err := <-closeErrCh:
if err != nil {
fmt.Println("Failed to close server:", err)
}
case <-time.After(10 * time.Second):
fmt.Println("Timeout when closing, printing traces:")
buf := make([]byte, 1<<20)
n := runtime.Stack(buf, true)
blocks := strings.Split(string(buf[:n]), "\n\n")
for _, block := range blocks {
if strings.Contains(block, "github.com/xtls/xray-core/core.(*Instance).Close") {
fmt.Println(block)
}
}
}
}()
// Explicitly triggering GC to remove garbage from config loading. // Explicitly triggering GC to remove garbage from config loading.
runtime.GC() runtime.GC()