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

Reply via email to