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  }