Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pkg/lockservice/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,29 @@ func (l Lock) addHolder(
logHolderAdded(logger, c, l)
}

// setMode updates the lock's mode to match the requested mode. Returns the
// updated Lock and whether the mode actually changed. Because Lock is a value
// type, the caller must write the returned Lock back to the store when changed.
//
// This must be called when a waiter is promoted to holder, because addHolder
// operates on a value copy and cannot update the mode in the store. Without
// this, the stored mode becomes stale:
// - Shared lock with Exclusive holder → new Shared requests incorrectly allowed
// - Exclusive lock with Shared holder → new Shared requests incorrectly blocked
func (l Lock) setMode(mode pb.LockMode) (Lock, bool) {
if mode == pb.LockMode_Exclusive && l.isShared() {
l.value &^= flagLockSharedMode
l.value |= flagLockExclusiveMode
return l, true
}
if mode == pb.LockMode_Shared && !l.isShared() {
l.value &^= flagLockExclusiveMode
l.value |= flagLockSharedMode
return l, true
}
return l, false
}

func (l Lock) isEmpty() bool {
return l.holders.size() == 0 &&
(l.waiters == nil || l.waiters.size() == 0)
Expand Down
63 changes: 63 additions & 0 deletions pkg/lockservice/lock_table_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,15 @@ func (l *localLockTable) acquireRowLockLocked(c *lockContext) error {
// only new holder can added lock into txn.
// newHolder is false means prev op of txn has already added lock into txn
if newHolder {
if updated, changed := lock.setMode(c.opts.Mode); changed {
l.mu.store.Add(key, updated)
// Range lock is stored as two entries (start + end) with
// independent Lock.value bytes. When we update the mode on
// one end we must also update the paired entry so that
// subsequent isLockModeAllowed checks on either key see the
// correct mode.
l.setModePairedRangeLock(key, updated, c.opts.Mode)
}
err := c.txn.lockAdded(l.bind.Group, l.bind, [][]byte{key}, l.logger)
if err != nil {
return err
Expand Down Expand Up @@ -649,6 +658,13 @@ func (l *localLockTable) addRangeLockLocked(
// only new holder can added lock into txn.
// newHolder is false means prev op of txn has already added lock into txn
if newHolder {
if updated, changed := conflictWith.setMode(c.opts.Mode); changed {
l.mu.store.Add(conflictKey, updated)
// Range lock is stored as two entries (start + end) with
// independent Lock.value bytes. Update the paired entry
// so both ends reflect the correct mode.
l.setModePairedRangeLock(conflictKey, updated, c.opts.Mode)
}
err := c.txn.lockAdded(l.bind.Group, l.bind, [][]byte{conflictKey}, l.logger)
if err != nil {
return nil, Lock{}, err
Expand Down Expand Up @@ -766,6 +782,53 @@ func (l *localLockTable) mustGetRangeStart(endKey []byte) []byte {
return v
}

// setModePairedRangeLock updates the mode of the paired range lock entry.
// A range lock is stored as two entries (range-start and range-end) with
// independent Lock.value bytes. When setMode updates one end, this helper
// finds and updates the other end so both entries have a consistent mode.
// It is a no-op for row locks.
func (l *localLockTable) setModePairedRangeLock(key []byte, lock Lock, mode pb.LockMode) {
if lock.isLockRow() {
return
}
if lock.isLockRangeEnd() {
// Find the paired range-start via Prev. Between range-start and
// range-end there may be row locks from other transactions, so we
// scan backwards until we find a range-start entry.
cur := key
for {
prevKey, prevLock, ok := l.mu.store.Prev(cur)
if !ok {
return
}
if prevLock.isLockRangeStart() {
if updated, changed := prevLock.setMode(mode); changed {
l.mu.store.Add(prevKey, updated)
}
return
}
cur = prevKey
}
} else if lock.isLockRangeStart() {
// Find the paired range-end. Between range-start and range-end
// there may be row locks from other transactions, so we scan
// forward until we find a range-end entry.
l.mu.store.Range(
nextKey(key, nil),
nil,
func(k []byte, v Lock) bool {
if v.isLockRangeEnd() {
if updated, changed := v.setMode(mode); changed {
l.mu.store.Add(k, updated)
}
return false // stop
}
return true // keep scanning
},
)
}
}

func nextKey(src, dst []byte) []byte {
dst = append(dst, src...)
dst = append(dst, 0)
Expand Down
Loading
Loading