github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/limiter/limiter.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache-2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package limiter implements a concurrency limiter with support for
     6  // contexts.
     7  package limiter
     8  
     9  import "context"
    10  
    11  // A Limiter enforces concurrency limits among a set of goroutines.
    12  // It maintains a bucket of tokens; a number of tokens (e.g.,
    13  // representing the cost of an operation) must be acquired by a
    14  // goroutine before proceeding. A limiter is not fair: tokens are not
    15  // granted in FIFO order; rather, waiters are picked randomly to be
    16  // granted new tokens.
    17  //
    18  // A nil limiter issues an infinite number of tokens.
    19  type Limiter struct {
    20  	c      chan int
    21  	waiter chan struct{}
    22  }
    23  
    24  // New creates a new limiter with 0 tokens.
    25  func New() *Limiter {
    26  	l := &Limiter{make(chan int, 1), make(chan struct{}, 1)}
    27  	l.waiter <- struct{}{}
    28  	return l
    29  }
    30  
    31  // Acquire blocks until the goroutine is granted the desired number
    32  // of tokens, or until the context is done.
    33  func (l *Limiter) Acquire(ctx context.Context, need int) error {
    34  	if l == nil {
    35  		return ctx.Err()
    36  	}
    37  	select {
    38  	case <-l.waiter:
    39  	case <-ctx.Done():
    40  		return ctx.Err()
    41  	}
    42  	defer func() {
    43  		l.waiter <- struct{}{}
    44  	}()
    45  
    46  	var have int
    47  	for {
    48  		select {
    49  		case n := <-l.c:
    50  			have += n
    51  			if m := have - need; m >= 0 {
    52  				l.Release(m)
    53  				return nil
    54  			}
    55  		case <-ctx.Done():
    56  			l.Release(have)
    57  			return ctx.Err()
    58  		}
    59  	}
    60  }
    61  
    62  // Release adds a number of tokens back into the limiter.
    63  func (l *Limiter) Release(n int) {
    64  	if l == nil {
    65  		return
    66  	}
    67  	if n == 0 {
    68  		return
    69  	}
    70  	for {
    71  		select {
    72  		case l.c <- n:
    73  			return
    74  		case have := <-l.c:
    75  			n += have
    76  		}
    77  	}
    78  }
    79  
    80  type LimiterIfc interface {
    81  	Release(n int)
    82  	Acquire(ctx context.Context, need int) error
    83  }