From 31f1ef35b467373a9d83bc29215005b0436b8636 Mon Sep 17 00:00:00 2001 From: liangxu Date: Wed, 29 Apr 2026 00:50:10 +0800 Subject: [PATCH] fix(ops-api/audit): guard ip2region searcher with per-version mutex The buffer-backed xdb.Searcher is not safe for concurrent use, so parallel audit appends could race on Search and corrupt internal state. Serialize v4 and v6 lookups behind separate mutexes so concurrent callers fall through one at a time. Co-Authored-By: Claude Opus 4.7 (1M context) --- server/internal/ops/app/ip_region.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/server/internal/ops/app/ip_region.go b/server/internal/ops/app/ip_region.go index 189a1dc..4bdb3dd 100644 --- a/server/internal/ops/app/ip_region.go +++ b/server/internal/ops/app/ip_region.go @@ -7,6 +7,7 @@ import ( "net" "os" "strings" + "sync" "go.uber.org/zap" @@ -152,8 +153,10 @@ func (r *IPRegionResolver) Lookup(rawIP string) string { } type bufferIPRegionSearcher struct { - v4 *xdb.Searcher - v6 *xdb.Searcher + v4 *xdb.Searcher + v4Mu sync.Mutex + v6 *xdb.Searcher + v6Mu sync.Mutex } func (s *bufferIPRegionSearcher) Search(ip any) (string, error) { @@ -166,11 +169,15 @@ func (s *bufferIPRegionSearcher) Search(ip any) (string, error) { if s == nil || s.v4 == nil { return "", nil } + s.v4Mu.Lock() + defer s.v4Mu.Unlock() return s.v4.Search(ipBytes) case xdb.IPv6.Bytes: if s == nil || s.v6 == nil { return "", nil } + s.v6Mu.Lock() + defer s.v6Mu.Unlock() return s.v6.Search(ipBytes) default: return "", fmt.Errorf("invalid byte ip address with len=%d", len(ipBytes))