From 5053927df423e5b4a0457b0ce40e710d97b0bac0 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 19 Mar 2026 13:10:02 -0700 Subject: [PATCH] fix(mount): correct operator precedence in shared lock wait condition The shared lock wait loop on line 88 had a parenthesization bug that caused the `|| entry.activeExclusiveLockOwnerCount > 0` check to fall outside the `!lock.isDeleted` guard. Due to Go operator precedence (&& binds tighter than ||), the condition evaluated as: (!lock.isDeleted && (waiters_check)) || exclusiveCount > 0 This meant a deleted (cancelled) shared lock waiter could never exit the wait loop while any exclusive lock was active on the same key, since the isDeleted check was bypassed. The goroutine would spin in cond.Wait() forever, leaking and potentially cascading to block other FUSE operations on the same file handle. The fix adds parentheses to match the exclusive lock condition on line 84, so isDeleted properly gates the entire wait expression: !lock.isDeleted && ((waiters_check) || exclusiveCount > 0) This bug has existed since commit c43238b30 ("fix waiting condition") but became more likely to trigger in recent versions due to increased lock contention from parallel chunk fetching (#7569), directory handle mutex (#7674), and metadata cache snapshot consistency (#8531). Fixes #8696 --- weed/util/lock_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weed/util/lock_table.go b/weed/util/lock_table.go index a932ae5b1..f52d1179e 100644 --- a/weed/util/lock_table.go +++ b/weed/util/lock_table.go @@ -85,7 +85,7 @@ func (lt *LockTable[T]) AcquireLock(intention string, key T, lockType LockType) entry.cond.Wait() } } else { - for !lock.isDeleted && (len(entry.waiters) > 0 && lock.ID != entry.waiters[0].ID) || entry.activeExclusiveLockOwnerCount > 0 { + for !lock.isDeleted && ((len(entry.waiters) > 0 && lock.ID != entry.waiters[0].ID) || entry.activeExclusiveLockOwnerCount > 0) { entry.cond.Wait() } }