github.com/xmidt-org/webpa-common@v1.11.9/store/circularPool.go (about)

     1  package store
     2  
     3  import (
     4  	"errors"
     5  	"time"
     6  )
     7  
     8  var (
     9  	ErrorInvalidMaxSize     = errors.New("The maxSize of a pool must be positive")
    10  	ErrorInvalidInitialSize = errors.New("The initialSize of a pool cannot be larger than maxSize")
    11  	ErrorNewRequired        = errors.New("A new function is required")
    12  	ErrorPoolClosed         = errors.New("The pool is already closed")
    13  )
    14  
    15  // Pool represents an object pool.  sync.Pool satisfies this interface,
    16  // as does CircularPool from this package.
    17  type Pool interface {
    18  	// Get fetches an object from the pool.  Implementations will vary on
    19  	// the behavior of an empty pool.
    20  	Get() interface{}
    21  
    22  	// Put inserts and object into the pool.  Get and Put are typically used
    23  	// with the same type, though that is not necessarily enforced.
    24  	Put(value interface{})
    25  }
    26  
    27  // CircularPool represents a fixed-size pool whose objects rotate in
    28  // and out.  Like sync.Pool, clients should not expect any relationship
    29  // between objects obtained from a CircularPool and objects put into a
    30  // CircularPool.
    31  //
    32  // Get will block until an object is returned to the pool. GetOrNew, however,
    33  // will create a new object if the pool is exhausted.  This allows a client a
    34  // choice between being limited by the pool or treating the pool as the minimum
    35  // amount of objects to keep around.
    36  //
    37  // Put will drop objects if invoked when the pool is full.  This can happen
    38  // if a client uses GetOrNew.
    39  //
    40  // The main difference between CircularPool and sync.Pool is that objects within
    41  // a CircularPool will not get deallocated.  This makes a CircularPool appropriate
    42  // for times when a statically-sized pool of permanent objects is desired.
    43  //
    44  // A CircularPool is a very flexible concurrent data structure.  It can be used as a simple pool
    45  // of objects for canonicalization.  It can also be used for rate limiting or any situation where
    46  // a lease is required.
    47  type CircularPool interface {
    48  	Pool
    49  
    50  	// TryGet will never block.  It will attempt to grab an object from the pool,
    51  	// returning nil and false if it cannot do so.
    52  	TryGet() (interface{}, bool)
    53  
    54  	// GetCancel will wait for an available object with cancellation semantics
    55  	// similar to golang's Context.  Any activity, such as close, on the supplied
    56  	// channel will interrupt the wait and this method will return nil and false.
    57  	GetCancel(<-chan struct{}) (interface{}, bool)
    58  
    59  	// GetTimeout will wait for an available object for the specified timeout.
    60  	// If the timeout is not positive, it behaves exactly as TryGet.  Otherwise, this
    61  	// method waits the given duration for an available object, returning nil
    62  	// and false if the timeout elapses without an available object.
    63  	GetTimeout(time.Duration) (interface{}, bool)
    64  
    65  	// GetOrNew will never block.  If the pool is exhausted, the new function
    66  	// will be used to create a new object.  This can be used to allow the number
    67  	// objects to grow beyond the size of the pool.  However, when Put is called,
    68  	// and this pool is full, the object is dropped.
    69  	GetOrNew() interface{}
    70  }
    71  
    72  // NewCircularPool creates a fixed-size rotating object pool.  maxSize is the maximum
    73  // number of objects in the pool, while initialSize controls how many objects are preallocated.
    74  // Setting initialSize to 0 yields an initially empty pool.  If maxSize < 1 or initialSize > maxSize,
    75  // and error is returned.
    76  //
    77  // The new function is used to create objects for the pool.  This function cannot be nil, or an
    78  // error is returned, and must be safe for concurrent execution.  If initialSize > 0, then
    79  // the new function is used to preallocate objects for the pool.
    80  //
    81  // For an initially empty pool, be aware that Get will block forever.  In that case, another
    82  // goroutine must call Put in order to release the goroutine waiting on Get.  Initially empty
    83  // pools are appropriate as concurrent barriers, for example.
    84  func NewCircularPool(initialSize, maxSize int, new func() interface{}) (CircularPool, error) {
    85  	if maxSize < 1 {
    86  		return nil, ErrorInvalidMaxSize
    87  	} else if initialSize > maxSize {
    88  		return nil, ErrorInvalidInitialSize
    89  	} else if new == nil {
    90  		return nil, ErrorNewRequired
    91  	}
    92  
    93  	pool := &circularPool{
    94  		new:     new,
    95  		objects: make(chan interface{}, maxSize),
    96  	}
    97  
    98  	for preallocate := 0; preallocate < initialSize; preallocate++ {
    99  		pool.objects <- pool.new()
   100  	}
   101  
   102  	return pool, nil
   103  }
   104  
   105  // circularPool is the internal implementation of CircularPool.  It's based around a channel
   106  // that stores the objects.
   107  type circularPool struct {
   108  	new     func() interface{}
   109  	objects chan interface{}
   110  }
   111  
   112  func (pool *circularPool) Get() interface{} {
   113  	return <-pool.objects
   114  }
   115  
   116  func (pool *circularPool) TryGet() (interface{}, bool) {
   117  	select {
   118  	case value := <-pool.objects:
   119  		return value, true
   120  	default:
   121  		return nil, false
   122  	}
   123  }
   124  
   125  func (pool *circularPool) GetCancel(done <-chan struct{}) (interface{}, bool) {
   126  	select {
   127  	case value := <-pool.objects:
   128  		return value, true
   129  	case <-done:
   130  		return nil, false
   131  	}
   132  }
   133  
   134  func (pool *circularPool) GetTimeout(timeout time.Duration) (interface{}, bool) {
   135  	if timeout < 1 {
   136  		return pool.TryGet()
   137  	}
   138  
   139  	timer := time.NewTimer(timeout)
   140  
   141  	// Stop() performs a tiny cleanup in the event
   142  	// that a value was obtained before the timer fired
   143  	defer timer.Stop()
   144  
   145  	select {
   146  	case value := <-pool.objects:
   147  		return value, true
   148  	case <-timer.C:
   149  		return nil, false
   150  	}
   151  }
   152  
   153  func (pool *circularPool) Put(value interface{}) {
   154  	select {
   155  	case pool.objects <- value:
   156  	default:
   157  	}
   158  }
   159  
   160  func (pool *circularPool) GetOrNew() interface{} {
   161  	select {
   162  	case value := <-pool.objects:
   163  		return value
   164  	default:
   165  		return pool.new()
   166  	}
   167  }