github.com/kjdelisle/consul@v1.4.5/lib/semaphore/semaphore.go (about)

     1  // Package semaphore implements a simple semaphore that is based on
     2  // golang.org/x/sync/semaphore but doesn't support weights. It's advantage over
     3  // a simple buffered chan is that the capacity of the semaphore (i.e. the number
     4  // of slots available) can be changed dynamically at runtime without waiting for
     5  // all existing work to stop. This makes it easier to implement e.g. concurrency
     6  // limits on certain operations that can be reconfigured at runtime.
     7  package semaphore
     8  
     9  import (
    10  	"container/list"
    11  	"context"
    12  	"sync"
    13  )
    14  
    15  // Dynamic implements a semaphore whose capacity can be changed dynamically at
    16  // run time.
    17  type Dynamic struct {
    18  	size    int64
    19  	cur     int64
    20  	waiters list.List
    21  	mu      sync.Mutex
    22  }
    23  
    24  // NewDynamic returns a dynamic semaphore with the given initial capacity. Note
    25  // that this is for convenience and to match golang.org/x/sync/semaphore however
    26  // it's possible to use a zero-value semaphore provided SetSize is called before
    27  // use.
    28  func NewDynamic(n int64) *Dynamic {
    29  	return &Dynamic{
    30  		size: n,
    31  	}
    32  }
    33  
    34  // SetSize dynamically updates the number of available slots. If there are more
    35  // than n slots currently acquired, no further acquires will succeed until
    36  // sufficient have been released to take the total outstanding below n again.
    37  func (s *Dynamic) SetSize(n int64) error {
    38  	s.mu.Lock()
    39  	defer s.mu.Unlock()
    40  	s.size = n
    41  	return nil
    42  }
    43  
    44  // Acquire attempts to acquire one "slot" in the semaphore, blocking only until
    45  // ctx is Done. On success, returns nil. On failure, returns ctx.Err() and leaves
    46  // the semaphore unchanged.
    47  //
    48  // If ctx is already done, Acquire may still succeed without blocking.
    49  func (s *Dynamic) Acquire(ctx context.Context) error {
    50  	s.mu.Lock()
    51  	if s.cur < s.size {
    52  		s.cur++
    53  		s.mu.Unlock()
    54  		return nil
    55  	}
    56  
    57  	// Need to wait, add to waiter list
    58  	ready := make(chan struct{})
    59  	elem := s.waiters.PushBack(ready)
    60  	s.mu.Unlock()
    61  
    62  	select {
    63  	case <-ctx.Done():
    64  		err := ctx.Err()
    65  		s.mu.Lock()
    66  		select {
    67  		case <-ready:
    68  			// Acquired the semaphore after we were canceled.  Rather than trying to
    69  			// fix up the queue, just pretend we didn't notice the cancellation.
    70  			err = nil
    71  		default:
    72  			s.waiters.Remove(elem)
    73  		}
    74  		s.mu.Unlock()
    75  		return err
    76  
    77  	case <-ready:
    78  		return nil
    79  	}
    80  }
    81  
    82  // Release releases the semaphore. It will panic if release is called on an
    83  // empty semphore.
    84  func (s *Dynamic) Release() {
    85  	s.mu.Lock()
    86  	defer s.mu.Unlock()
    87  
    88  	if s.cur < 1 {
    89  		panic("semaphore: bad release")
    90  	}
    91  
    92  	next := s.waiters.Front()
    93  
    94  	// If there are no waiters, just decrement and we're done
    95  	if next == nil {
    96  		s.cur--
    97  		return
    98  	}
    99  
   100  	// Need to yield our slot to the next waiter.
   101  	// Remove them from the list
   102  	s.waiters.Remove(next)
   103  	// And trigger it's chan before we release the lock
   104  	close(next.Value.(chan struct{}))
   105  	// Note we _don't_ decrement inflight since the slot was yielded directly.
   106  }