1/* 2Package locker provides a mechanism for creating finer-grained locking to help 3free up more global locks to handle other tasks. 4 5The implementation looks close to a sync.Mutex, however the user must provide a 6reference to use to refer to the underlying lock when locking and unlocking, 7and unlock may generate an error. 8 9If a lock with a given name does not exist when `Lock` is called, one is 10created. 11Lock references are automatically cleaned up on `Unlock` if nothing else is 12waiting for the lock. 13*/ 14package locker 15 16import ( 17 "errors" 18 "sync" 19 "sync/atomic" 20) 21 22// ErrNoSuchLock is returned when the requested lock does not exist 23var ErrNoSuchLock = errors.New("no such lock") 24 25// Locker provides a locking mechanism based on the passed in reference name 26type Locker struct { 27 mu sync.Mutex 28 locks map[string]*lockCtr 29} 30 31// lockCtr is used by Locker to represent a lock with a given name. 32type lockCtr struct { 33 mu sync.Mutex 34 // waiters is the number of waiters waiting to acquire the lock 35 // this is int32 instead of uint32 so we can add `-1` in `dec()` 36 waiters int32 37} 38 39// inc increments the number of waiters waiting for the lock 40func (l *lockCtr) inc() { 41 atomic.AddInt32(&l.waiters, 1) 42} 43 44// dec decrements the number of waiters waiting on the lock 45func (l *lockCtr) dec() { 46 atomic.AddInt32(&l.waiters, -1) 47} 48 49// count gets the current number of waiters 50func (l *lockCtr) count() int32 { 51 return atomic.LoadInt32(&l.waiters) 52} 53 54// Lock locks the mutex 55func (l *lockCtr) Lock() { 56 l.mu.Lock() 57} 58 59// Unlock unlocks the mutex 60func (l *lockCtr) Unlock() { 61 l.mu.Unlock() 62} 63 64// New creates a new Locker 65func New() *Locker { 66 return &Locker{ 67 locks: make(map[string]*lockCtr), 68 } 69} 70 71// Lock locks a mutex with the given name. If it doesn't exist, one is created 72func (l *Locker) Lock(name string) { 73 l.mu.Lock() 74 if l.locks == nil { 75 l.locks = make(map[string]*lockCtr) 76 } 77 78 nameLock, exists := l.locks[name] 79 if !exists { 80 nameLock = &lockCtr{} 81 l.locks[name] = nameLock 82 } 83 84 // increment the nameLock waiters while inside the main mutex 85 // this makes sure that the lock isn't deleted if `Lock` and `Unlock` are called concurrently 86 nameLock.inc() 87 l.mu.Unlock() 88 89 // Lock the nameLock outside the main mutex so we don't block other operations 90 // once locked then we can decrement the number of waiters for this lock 91 nameLock.Lock() 92 nameLock.dec() 93} 94 95// Unlock unlocks the mutex with the given name 96// If the given lock is not being waited on by any other callers, it is deleted 97func (l *Locker) Unlock(name string) error { 98 l.mu.Lock() 99 nameLock, exists := l.locks[name] 100 if !exists { 101 l.mu.Unlock() 102 return ErrNoSuchLock 103 } 104 105 if nameLock.count() == 0 { 106 delete(l.locks, name) 107 } 108 nameLock.Unlock() 109 110 l.mu.Unlock() 111 return nil 112} 113