github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/pkg/locker/locker.go (about) 1 /* 2 Package locker provides a mechanism for creating finer-grained locking to help 3 free up more global locks to handle other tasks. 4 5 The implementation looks close to a sync.Mutex, however the user must provide a 6 reference to use to refer to the underlying lock when locking and unlocking, 7 and unlock may generate an error. 8 9 If a lock with a given name does not exist when `Lock` is called, one is 10 created. 11 Lock references are automatically cleaned up on `Unlock` if nothing else is 12 waiting for the lock. 13 */ 14 package locker // import "github.com/demonoid81/moby/pkg/locker" 15 16 import ( 17 "errors" 18 "sync" 19 "sync/atomic" 20 ) 21 22 // ErrNoSuchLock is returned when the requested lock does not exist 23 var ErrNoSuchLock = errors.New("no such lock") 24 25 // Locker provides a locking mechanism based on the passed in reference name 26 type 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. 32 type 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 40 func (l *lockCtr) inc() { 41 atomic.AddInt32(&l.waiters, 1) 42 } 43 44 // dec decrements the number of waiters waiting on the lock 45 func (l *lockCtr) dec() { 46 atomic.AddInt32(&l.waiters, -1) 47 } 48 49 // count gets the current number of waiters 50 func (l *lockCtr) count() int32 { 51 return atomic.LoadInt32(&l.waiters) 52 } 53 54 // Lock locks the mutex 55 func (l *lockCtr) Lock() { 56 l.mu.Lock() 57 } 58 59 // Unlock unlocks the mutex 60 func (l *lockCtr) Unlock() { 61 l.mu.Unlock() 62 } 63 64 // New creates a new Locker 65 func 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 72 func (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 97 func (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 }