1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
package lock
import (
"errors"
"fmt"
"sync"
)
// Mutex holds a single mutex and whether it has been allocated.
type Mutex struct {
id uint32
lock sync.Mutex
allocated bool
}
// ID retrieves the ID of the mutex
func (m *Mutex) ID() uint32 {
return m.id
}
// Lock locks the mutex
func (m *Mutex) Lock() {
m.lock.Lock()
}
// Unlock unlocks the mutex
func (m *Mutex) Unlock() {
m.lock.Unlock()
}
// Free deallocates the mutex to allow its reuse
func (m *Mutex) Free() error {
m.allocated = false
return nil
}
// InMemoryManager is a lock manager that allocates and retrieves local-only
// locks - that is, they are not multiprocess. This lock manager is intended
// purely for unit and integration testing and should not be used in production
// deployments.
type InMemoryManager struct {
locks []*Mutex
numLocks uint32
localLock sync.Mutex
}
// NewInMemoryManager creates a new in-memory lock manager with the given number
// of locks.
func NewInMemoryManager(numLocks uint32) (Manager, error) {
if numLocks == 0 {
return nil, errors.New("must provide a non-zero number of locks")
}
manager := new(InMemoryManager)
manager.numLocks = numLocks
manager.locks = make([]*Mutex, numLocks)
var i uint32
for i = range numLocks {
lock := new(Mutex)
lock.id = i
manager.locks[i] = lock
}
return manager, nil
}
// AllocateLock allocates a lock from the manager.
func (m *InMemoryManager) AllocateLock() (Locker, error) {
m.localLock.Lock()
defer m.localLock.Unlock()
for _, lock := range m.locks {
if !lock.allocated {
lock.allocated = true
return lock, nil
}
}
return nil, errors.New("all locks have been allocated")
}
// RetrieveLock retrieves a lock from the manager.
func (m *InMemoryManager) RetrieveLock(id uint32) (Locker, error) {
if id >= m.numLocks {
return nil, fmt.Errorf("given lock ID %d is too large - this manager only supports lock indexes up to %d", id, m.numLocks-1)
}
return m.locks[id], nil
}
// AllocateAndRetrieveLock allocates a lock with the given ID (if not already in
// use) and returns it.
func (m *InMemoryManager) AllocateAndRetrieveLock(id uint32) (Locker, error) {
if id >= m.numLocks {
return nil, fmt.Errorf("given lock ID %d is too large - this manager only supports lock indexes up to %d", id, m.numLocks)
}
if m.locks[id].allocated {
return nil, fmt.Errorf("given lock ID %d is already in use, cannot reallocate", id)
}
m.locks[id].allocated = true
return m.locks[id], nil
}
// FreeAllLocks frees all locks.
// This function is DANGEROUS. Please read the full comment in locks.go before
// trying to use it.
func (m *InMemoryManager) FreeAllLocks() error {
for _, lock := range m.locks {
lock.allocated = false
}
return nil
}
// Get number of available locks
func (m *InMemoryManager) AvailableLocks() (*uint32, error) {
var count uint32
for _, lock := range m.locks {
if !lock.allocated {
count++
}
}
return &count, nil
}
// Get any locks that are presently being held.
// Useful for debugging deadlocks.
func (m *InMemoryManager) LocksHeld() ([]uint32, error) {
//nolint:prealloc
var locks []uint32
for _, lock := range m.locks {
if lock.lock.TryLock() {
lock.lock.Unlock()
continue
}
locks = append(locks, lock.ID())
}
return locks, nil
}
|