mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-05 14:02:00 +02:00
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
170 lines
4.5 KiB
Go
170 lines
4.5 KiB
Go
package util
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
)
|
|
|
|
// LockTable is a table of locks that can be acquired.
|
|
// Locks are acquired in order of request.
|
|
type LockTable[T comparable] struct {
|
|
lockIdSeq int64
|
|
mu sync.Mutex
|
|
locks map[T]*LockEntry
|
|
locksInFlight map[T]int
|
|
}
|
|
|
|
type LockEntry struct {
|
|
mu sync.Mutex
|
|
waiters []*ActiveLock // ordered waiters that are blocked by exclusive locks
|
|
activeSharedLockOwnerCount int32
|
|
activeExclusiveLockOwnerCount int32
|
|
cond *sync.Cond
|
|
}
|
|
|
|
type LockType int
|
|
|
|
const (
|
|
SharedLock LockType = iota
|
|
ExclusiveLock
|
|
)
|
|
|
|
type ActiveLock struct {
|
|
ID int64
|
|
isDeleted bool
|
|
intention string // for debugging
|
|
lockType LockType
|
|
}
|
|
|
|
func NewLockTable[T comparable]() *LockTable[T] {
|
|
return &LockTable[T]{
|
|
locks: make(map[T]*LockEntry),
|
|
locksInFlight: make(map[T]int),
|
|
}
|
|
}
|
|
|
|
func (lt *LockTable[T]) NewActiveLock(intention string, lockType LockType) *ActiveLock {
|
|
id := atomic.AddInt64(<.lockIdSeq, 1)
|
|
l := &ActiveLock{ID: id, intention: intention, lockType: lockType}
|
|
return l
|
|
}
|
|
|
|
func (lt *LockTable[T]) AcquireLock(intention string, key T, lockType LockType) (lock *ActiveLock) {
|
|
lt.mu.Lock()
|
|
// Get or create the lock entry for the key
|
|
entry, exists := lt.locks[key]
|
|
if !exists {
|
|
entry = &LockEntry{}
|
|
entry.cond = sync.NewCond(&entry.mu)
|
|
lt.locks[key] = entry
|
|
lt.locksInFlight[key] = 0
|
|
}
|
|
lt.locksInFlight[key]++
|
|
lt.mu.Unlock()
|
|
|
|
lock = lt.NewActiveLock(intention, lockType)
|
|
|
|
// If the lock is held exclusively, wait
|
|
entry.mu.Lock()
|
|
if len(entry.waiters) > 0 || lockType == ExclusiveLock || entry.activeExclusiveLockOwnerCount > 0 {
|
|
if glog.V(4) {
|
|
fmt.Printf("ActiveLock %d %s wait for %+v type=%v with waiters %d active r%d w%d.\n", lock.ID, lock.intention, key, lockType, len(entry.waiters), entry.activeSharedLockOwnerCount, entry.activeExclusiveLockOwnerCount)
|
|
if len(entry.waiters) > 0 {
|
|
for _, waiter := range entry.waiters {
|
|
fmt.Printf(" %d", waiter.ID)
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
}
|
|
entry.waiters = append(entry.waiters, lock)
|
|
if lockType == ExclusiveLock {
|
|
for !lock.isDeleted && ((len(entry.waiters) > 0 && lock.ID != entry.waiters[0].ID) || entry.activeExclusiveLockOwnerCount > 0 || entry.activeSharedLockOwnerCount > 0) {
|
|
entry.cond.Wait()
|
|
}
|
|
} else {
|
|
for !lock.isDeleted && ((len(entry.waiters) > 0 && lock.ID != entry.waiters[0].ID) || entry.activeExclusiveLockOwnerCount > 0) {
|
|
entry.cond.Wait()
|
|
}
|
|
}
|
|
// Remove the transaction from the waiters list
|
|
if len(entry.waiters) > 0 && lock.ID == entry.waiters[0].ID {
|
|
entry.waiters = entry.waiters[1:]
|
|
entry.cond.Broadcast()
|
|
}
|
|
}
|
|
|
|
// Otherwise, grant the lock
|
|
if glog.V(4) {
|
|
fmt.Printf("ActiveLock %d %s locked %+v type=%v with waiters %d active r%d w%d.\n", lock.ID, lock.intention, key, lockType, len(entry.waiters), entry.activeSharedLockOwnerCount, entry.activeExclusiveLockOwnerCount)
|
|
if len(entry.waiters) > 0 {
|
|
for _, waiter := range entry.waiters {
|
|
fmt.Printf(" %d", waiter.ID)
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
}
|
|
if lock.lockType == ExclusiveLock {
|
|
entry.activeExclusiveLockOwnerCount++
|
|
} else {
|
|
entry.activeSharedLockOwnerCount++
|
|
}
|
|
entry.mu.Unlock()
|
|
|
|
return lock
|
|
}
|
|
|
|
func (lt *LockTable[T]) ReleaseLock(key T, lock *ActiveLock) {
|
|
lt.mu.Lock()
|
|
defer lt.mu.Unlock()
|
|
|
|
entry, exists := lt.locks[key]
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
lt.locksInFlight[key]--
|
|
entry.mu.Lock()
|
|
defer entry.mu.Unlock()
|
|
|
|
// Remove the transaction from the waiters list
|
|
for i, waiter := range entry.waiters {
|
|
if waiter == lock {
|
|
waiter.isDeleted = true
|
|
entry.waiters = append(entry.waiters[:i], entry.waiters[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
if lock.lockType == ExclusiveLock {
|
|
entry.activeExclusiveLockOwnerCount--
|
|
} else {
|
|
entry.activeSharedLockOwnerCount--
|
|
}
|
|
|
|
// If there are no waiters, release the lock
|
|
if len(entry.waiters) == 0 && lt.locksInFlight[key] <= 0 && entry.activeExclusiveLockOwnerCount <= 0 && entry.activeSharedLockOwnerCount <= 0 {
|
|
delete(lt.locks, key)
|
|
delete(lt.locksInFlight, key)
|
|
}
|
|
|
|
if glog.V(4) {
|
|
fmt.Printf("ActiveLock %d %s unlocked %+v type=%v with waiters %d active r%d w%d.\n", lock.ID, lock.intention, key, lock.lockType, len(entry.waiters), entry.activeSharedLockOwnerCount, entry.activeExclusiveLockOwnerCount)
|
|
if len(entry.waiters) > 0 {
|
|
for _, waiter := range entry.waiters {
|
|
fmt.Printf(" %d", waiter.ID)
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
}
|
|
|
|
// Notify the next waiter
|
|
entry.cond.Broadcast()
|
|
}
|
|
|
|
func main() {
|
|
|
|
}
|