This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch pr-1128 in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
commit fcbe4898ba8110aaa6709c6b60edbcc925737257 Author: Hongtao Gao <[email protected]> AuthorDate: Fri May 15 01:49:01 2026 +0000 fix(storage): guard closeIdleSegments against refcount steal across ticks Capture refCount at CAS-bump time and only issue the close DecRef when the snapshot proved refCount==1 (baseline only). Without this guard, a segment held by an active query could have its ref stolen, causing performCleanup to fire under active use on a subsequent reclaimer tick. Also fix closedCount to only count segments closed in this tick rather than all segments with refCount==0 (which included already-closed ones). via [HAPI](https://hapi.run) Co-Authored-By: HAPI <[email protected]> --- banyand/internal/storage/segment.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/banyand/internal/storage/segment.go b/banyand/internal/storage/segment.go index 6b28e5367..e27b7e4c0 100644 --- a/banyand/internal/storage/segment.go +++ b/banyand/internal/storage/segment.go @@ -494,8 +494,10 @@ func (sc *segmentController[T, O]) closeIdleSegments() int { sc.RLock() segs := make([]*segment[T, O], 0, len(sc.lst)) bumped := make([]bool, 0, len(sc.lst)) + refAtBump := make([]int32, 0, len(sc.lst)) for _, s := range sc.lst { didBump := false + var snapRef int32 for { current := atomic.LoadInt32(&s.refCount) if current <= 0 { @@ -503,25 +505,31 @@ func (sc *segmentController[T, O]) closeIdleSegments() int { } if atomic.CompareAndSwapInt32(&s.refCount, current, current+1) { didBump = true + snapRef = current break } } segs = append(segs, s) bumped = append(bumped, didBump) + refAtBump = append(refAtBump, snapRef) } sc.RUnlock() closedCount := 0 for i, seg := range segs { - if bumped[i] { - if seg.lastAccessed.Load() < idleThreshold { - seg.DecRef() - } - seg.DecRef() + if !bumped[i] { + continue } - if atomic.LoadInt32(&seg.refCount) == 0 { + // Only close when the snapshot proved refCount==1 (baseline only): + // a higher value means other callers hold active references, and + // decrementing past our bump would steal one of their refs, + // potentially triggering performCleanup under active use on a + // subsequent tick. + if refAtBump[i] == 1 && seg.lastAccessed.Load() < idleThreshold { + seg.DecRef() closedCount++ } + seg.DecRef() } return closedCount
