chrislu
1 year ago
2 changed files with 188 additions and 0 deletions
@ -0,0 +1,146 @@ |
|||||
|
package util |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"sync" |
||||
|
"sync/atomic" |
||||
|
) |
||||
|
|
||||
|
// LockTable is a table of locks that can be acquired.
|
||||
|
// Locks are acquired in order of request.
|
||||
|
type LockTable[T comparable] struct { |
||||
|
mu sync.Mutex |
||||
|
locks map[T]*LockEntry |
||||
|
lockIdSeq int64 |
||||
|
} |
||||
|
|
||||
|
type LockEntry struct { |
||||
|
mu sync.Mutex |
||||
|
waiters []*ActiveLock // ordered waiters that are blocked by exclusive locks
|
||||
|
activeLockOwnerCount int32 |
||||
|
lockType LockType |
||||
|
cond *sync.Cond |
||||
|
} |
||||
|
|
||||
|
type LockType int |
||||
|
|
||||
|
const ( |
||||
|
SharedLock LockType = iota |
||||
|
ExclusiveLock |
||||
|
) |
||||
|
|
||||
|
type ActiveLock struct { |
||||
|
ID int64 |
||||
|
isDeleted bool |
||||
|
intention string // for debugging
|
||||
|
} |
||||
|
|
||||
|
func NewLockTable[T comparable]() *LockTable[T] { |
||||
|
return &LockTable[T]{ |
||||
|
locks: make(map[T]*LockEntry), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (lt *LockTable[T]) NewActiveLock(intention string) *ActiveLock { |
||||
|
id := atomic.AddInt64(<.lockIdSeq, 1) |
||||
|
l := &ActiveLock{ID: id, intention: intention} |
||||
|
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.mu.Unlock() |
||||
|
|
||||
|
lock = lt.NewActiveLock(intention) |
||||
|
|
||||
|
// If the lock is held exclusively, wait
|
||||
|
entry.mu.Lock() |
||||
|
if len(entry.waiters) > 0 || lockType == ExclusiveLock { |
||||
|
fmt.Printf("ActiveLock %d %s wait for %+v type=%v with waiters %d active %d.\n", lock.ID, lock.intention, key, lockType, len(entry.waiters), entry.activeLockOwnerCount) |
||||
|
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.activeLockOwnerCount > 0) { |
||||
|
entry.cond.Wait() |
||||
|
} |
||||
|
} else { |
||||
|
for !lock.isDeleted && (len(entry.waiters) > 0 && lock.ID != entry.waiters[0].ID) { |
||||
|
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() |
||||
|
} |
||||
|
} |
||||
|
entry.activeLockOwnerCount++ |
||||
|
|
||||
|
// Otherwise, grant the lock
|
||||
|
entry.lockType = lockType |
||||
|
fmt.Printf("ActiveLock %d %s locked %+v type=%v with waiters %d active %d.\n", lock.ID, lock.intention, key, lockType, len(entry.waiters), entry.activeLockOwnerCount) |
||||
|
if len(entry.waiters) > 0 { |
||||
|
for _, waiter := range entry.waiters { |
||||
|
fmt.Printf(" %d", waiter.ID) |
||||
|
} |
||||
|
fmt.Printf("\n") |
||||
|
} |
||||
|
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 |
||||
|
} |
||||
|
|
||||
|
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 there are no waiters, release the lock
|
||||
|
if len(entry.waiters) == 0 { |
||||
|
delete(lt.locks, key) |
||||
|
} |
||||
|
|
||||
|
fmt.Printf("ActiveLock %d %s unlocked %+v type=%v with waiters %d active %d.\n", lock.ID, lock.intention, key, entry.lockType, len(entry.waiters), entry.activeLockOwnerCount) |
||||
|
if len(entry.waiters) > 0 { |
||||
|
for _, waiter := range entry.waiters { |
||||
|
fmt.Printf(" %d", waiter.ID) |
||||
|
} |
||||
|
fmt.Printf("\n") |
||||
|
} |
||||
|
entry.activeLockOwnerCount-- |
||||
|
|
||||
|
// Notify the next waiter
|
||||
|
entry.cond.Broadcast() |
||||
|
} |
||||
|
|
||||
|
func main() { |
||||
|
|
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package util |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"math/rand" |
||||
|
"sync" |
||||
|
"testing" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
func TestOrderedLock(t *testing.T) { |
||||
|
lt := NewLockTable[string]() |
||||
|
|
||||
|
var wg sync.WaitGroup |
||||
|
// Simulate transactions requesting locks
|
||||
|
for i := 1; i <= 50; i++ { |
||||
|
wg.Add(1) |
||||
|
go func(i int) { |
||||
|
defer wg.Done() |
||||
|
key := "resource" |
||||
|
lockType := SharedLock |
||||
|
if i%5 == 0 { |
||||
|
lockType = ExclusiveLock |
||||
|
} |
||||
|
|
||||
|
// Simulate attempting to acquire the lock
|
||||
|
lock := lt.AcquireLock("", key, lockType) |
||||
|
|
||||
|
// Lock acquired, perform some work
|
||||
|
fmt.Printf("ActiveLock %d acquired the lock.\n", lock.ID) |
||||
|
|
||||
|
// Simulate some work
|
||||
|
time.Sleep(time.Duration(rand.Int31n(10)*10) * time.Millisecond) |
||||
|
|
||||
|
// Release the lock
|
||||
|
lt.ReleaseLock(key, lock) |
||||
|
fmt.Printf("ActiveLock %d released the lock.\n", lock.ID) |
||||
|
}(i) |
||||
|
} |
||||
|
|
||||
|
wg.Wait() |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue