github.com/GoWebProd/gip@v0.0.0-20230623090727-b60d41d5d320/pool/chain.go (about)

     1  package pool
     2  
     3  import (
     4  	"sync/atomic"
     5  	"unsafe"
     6  )
     7  
     8  // poolChain is a dynamically-sized version of poolDequeue.
     9  //
    10  // This is implemented as a doubly-linked list queue of poolDequeues
    11  // where each dequeue is double the size of the previous one. Once a
    12  // dequeue fills up, this allocates a new one and only ever pushes to
    13  // the latest dequeue. Pops happen from the other end of the list and
    14  // once a dequeue is exhausted, it gets removed from the list.
    15  type poolChain[T any] struct {
    16  	// head is the poolDequeue to push to. This is only accessed
    17  	// by the producer, so doesn't need to be synchronized.
    18  	head *poolChainElt[T]
    19  
    20  	// tail is the poolDequeue to popTail from. This is accessed
    21  	// by consumers, so reads and writes must be atomic.
    22  	tail *poolChainElt[T]
    23  }
    24  
    25  func (c *poolChain[T]) pushHead(val *T) {
    26  	d := c.head
    27  	if d == nil {
    28  		// Initialize the chain.
    29  		const initSize = 8 // Must be a power of 2
    30  		d = new(poolChainElt[T])
    31  		d.vals = make([]*T, initSize)
    32  		c.head = d
    33  		storePoolChainElt(&c.tail, d)
    34  	}
    35  
    36  	if d.pushHead(val) {
    37  		return
    38  	}
    39  
    40  	// The current dequeue is full. Allocate a new one of twice
    41  	// the size.
    42  	newSize := len(d.vals) * 2
    43  	if newSize >= dequeueLimit {
    44  		// Can't make it any bigger.
    45  		newSize = dequeueLimit
    46  	}
    47  
    48  	d2 := &poolChainElt[T]{prev: d}
    49  	d2.vals = make([]*T, newSize)
    50  	c.head = d2
    51  	storePoolChainElt(&d.next, d2)
    52  	d2.pushHead(val)
    53  }
    54  
    55  func (c *poolChain[T]) popHead() (*T, bool) {
    56  	d := c.head
    57  	for d != nil {
    58  		if val, ok := d.popHead(); ok {
    59  			return val, ok
    60  		}
    61  		// There may still be unconsumed elements in the
    62  		// previous dequeue, so try backing up.
    63  		d = loadPoolChainElt(&d.prev)
    64  	}
    65  	return nil, false
    66  }
    67  
    68  func (c *poolChain[T]) popTail() (*T, bool) {
    69  	d := loadPoolChainElt(&c.tail)
    70  	if d == nil {
    71  		return nil, false
    72  	}
    73  
    74  	for {
    75  		// It's important that we load the next pointer
    76  		// *before* popping the tail. In general, d may be
    77  		// transiently empty, but if next is non-nil before
    78  		// the pop and the pop fails, then d is permanently
    79  		// empty, which is the only condition under which it's
    80  		// safe to drop d from the chain.
    81  		d2 := loadPoolChainElt(&d.next)
    82  
    83  		if val, ok := d.popTail(); ok {
    84  			return val, ok
    85  		}
    86  
    87  		if d2 == nil {
    88  			// This is the only dequeue. It's empty right
    89  			// now, but could be pushed to in the future.
    90  			return nil, false
    91  		}
    92  
    93  		// The tail of the chain has been drained, so move on
    94  		// to the next dequeue. Try to drop it from the chain
    95  		// so the next pop doesn't have to look at the empty
    96  		// dequeue again.
    97  		if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.tail)), unsafe.Pointer(d), unsafe.Pointer(d2)) {
    98  			// We won the race. Clear the prev pointer so
    99  			// the garbage collector can collect the empty
   100  			// dequeue and so popHead doesn't back up
   101  			// further than necessary.
   102  			storePoolChainElt(&d2.prev, nil)
   103  		}
   104  		d = d2
   105  	}
   106  }