Add process information cache to avoid duplicate lookups

PreMatch and full match phases each created a fresh InboundContext,
causing process search (expensive OS syscalls) to run twice per
connection. Use a freelru ShardedLRU cache with 200ms TTL to serve
the second lookup from cache.
This commit is contained in:
世界
2026-03-23 14:26:45 +08:00
parent c13faa8e3c
commit b8e5a71450
3 changed files with 45 additions and 2 deletions

34
route/process_cache.go Normal file
View File

@@ -0,0 +1,34 @@
package route
import (
"context"
"net/netip"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
)
type processCacheKey struct {
Network string
Source netip.AddrPort
Destination netip.AddrPort
}
type processCacheEntry struct {
result *adapter.ConnectionOwner
err error
}
func (r *Router) findProcessInfoCached(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*adapter.ConnectionOwner, error) {
key := processCacheKey{
Network: network,
Source: source,
Destination: destination,
}
if entry, ok := r.processCache.Get(key); ok {
return entry.result, entry.err
}
result, err := process.FindProcessInfo(r.processSearcher, ctx, network, source, destination)
r.processCache.Add(key, processCacheEntry{result: result, err: err})
return result, err
}

View File

@@ -9,7 +9,6 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant"
R "github.com/sagernet/sing-box/route/rule"
@@ -415,7 +414,7 @@ func (r *Router) matchRule(
} else if metadata.Destination.IsIP() {
originDestination = metadata.Destination.AddrPort()
}
processInfo, fErr := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination)
processInfo, fErr := r.findProcessInfoCached(ctx, metadata.Network, metadata.Source.AddrPort(), originDestination)
if fErr != nil {
r.logger.InfoContext(ctx, "failed to search process: ", fErr)
} else {

View File

@@ -4,6 +4,7 @@ import (
"context"
"os"
"runtime"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
@@ -12,8 +13,11 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/task"
"github.com/sagernet/sing/contrab/freelru"
"github.com/sagernet/sing/contrab/maphash"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
@@ -34,6 +38,7 @@ type Router struct {
ruleSets []adapter.RuleSet
ruleSetMap map[string]adapter.RuleSet
processSearcher process.Searcher
processCache freelru.Cache[processCacheKey, processCacheEntry]
pauseManager pause.Manager
trackers []adapter.ConnectionTracker
platformInterface adapter.PlatformInterface
@@ -141,6 +146,11 @@ func (r *Router) Start(stage adapter.StartStage) error {
}
}
}
if r.processSearcher != nil {
processCache := common.Must1(freelru.NewSharded[processCacheKey, processCacheEntry](256, maphash.NewHasher[processCacheKey]().Hash32))
processCache.SetLifetime(200 * time.Millisecond)
r.processCache = processCache
}
case adapter.StartStatePostStart:
for i, rule := range r.rules {
monitor.Start("initialize rule[", i, "]")