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

     1  package queue
     2  
     3  import (
     4  	"sync/atomic"
     5  )
     6  
     7  const (
     8  	bufferBits = 32
     9  
    10  	// bufferLimit is the maximum size of a Buffer.
    11  	//
    12  	// This must be at most (1<<bufferBits)/2 because detecting fullness
    13  	// depends on wrapping around the ring buffer without wrapping around
    14  	// the index. We divide by 4 so this fits in an int on 32-bit.
    15  	bufferLimit = (1 << bufferBits) / 4
    16  )
    17  
    18  type Buffer[T any] struct {
    19  	// headTail packs together a 32-bit head index and a 32-bit
    20  	// tail index. Both are indexes into vals modulo len(vals)-1.
    21  	//
    22  	// tail = index of oldest data in buffer
    23  	// head = index of next slot to fill
    24  	//
    25  	// Slots in the range [tail, head) are owned by consumers.
    26  	// A consumer continues to own a slot outside this range until
    27  	// it nils the slot, at which point ownership passes to the
    28  	// producer.
    29  	//
    30  	// The head index is stored in the most-significant bits so
    31  	// that we can atomically add to it and the overflow is
    32  	// harmless.
    33  	headTail uint64
    34  
    35  	// vals is a ring buffer of pointers to values.
    36  	// The size of this must be a power of 2.
    37  	//
    38  	// vals[i] is nil if the slot is empty and non-nil
    39  	// otherwise. A slot is still in use until *both* the tail
    40  	// index has moved beyond it set to nil. This
    41  	// is set to nil atomically by the consumer and read
    42  	// atomically by the producer.
    43  	vals []*T
    44  }
    45  
    46  func New[T any](size int) *Buffer[T] {
    47  	return &Buffer[T]{
    48  		vals: make([]*T, size),
    49  	}
    50  }
    51  
    52  func (d *Buffer[T]) pack(head, tail uint32) uint64 {
    53  	const mask = 1<<bufferBits - 1
    54  	return (uint64(head) << bufferBits) | uint64(tail&mask)
    55  }
    56  
    57  func (d *Buffer[T]) unpack(ptrs uint64) (head, tail uint32) {
    58  	const mask = 1<<bufferBits - 1
    59  	head = uint32((ptrs >> bufferBits) & mask)
    60  	tail = uint32(ptrs & mask)
    61  	return
    62  }
    63  
    64  // Put adds val at the head of the queue. It returns false if the
    65  // queue is full. It must only be called by a single producer.
    66  func (d *Buffer[T]) Put(val *T) bool {
    67  	ptrs := atomic.LoadUint64(&d.headTail)
    68  	head, tail := d.unpack(ptrs)
    69  	
    70  	if (tail+uint32(len(d.vals)))&(1<<bufferBits-1) == head {
    71  		// Queue is full.
    72  		return false
    73  	}
    74  	
    75  	slot := &d.vals[head&uint32(len(d.vals)-1)]
    76  	// Check if the head slot has been released by popTail.
    77  	if *slot != nil {
    78  		// Another goroutine is still cleaning up the tail, so
    79  		// the queue is actually still full.
    80  		return false
    81  	}
    82  
    83  	*slot = val
    84  
    85  	// Increment head. This passes ownership of slot to popTail
    86  	// and acts as a store barrier for writing the slot.
    87  	atomic.AddUint64(&d.headTail, 1<<bufferBits)
    88  	return true
    89  }
    90  
    91  func (d *Buffer[T]) Take() (*T, bool) {
    92  	var slot **T
    93  	for {
    94  		ptrs := atomic.LoadUint64(&d.headTail)
    95  		head, tail := d.unpack(ptrs)
    96  		if tail == head {
    97  			// Queue is empty.
    98  			return nil, false
    99  		}
   100  
   101  		// Confirm head and tail (for our speculative check
   102  		// above) and increment tail. If this succeeds, then
   103  		// we own the slot at tail.
   104  		ptrs2 := d.pack(head, tail+1)
   105  		if atomic.CompareAndSwapUint64(&d.headTail, ptrs, ptrs2) {
   106  			// Success.
   107  			slot = &d.vals[tail&uint32(len(d.vals)-1)]
   108  			break
   109  		}
   110  	}
   111  
   112  	// We now own slot.
   113  	val := *slot
   114  
   115  	// Tell pushHead that we're done with this slot. Zeroing the
   116  	// slot is also important so we don't leave behind references
   117  	// that could keep this object live longer than necessary.
   118  	//
   119  	// We write to val first and then publish that we're done with
   120  	// this slot by atomically writing to typ.
   121  	*slot = nil
   122  
   123  	return val, true
   124  }
   125  
   126  func (d *Buffer[T]) Size() int {
   127  	ptrs := atomic.LoadUint64(&d.headTail)
   128  	head, tail := d.unpack(ptrs)
   129  
   130  	size := int(head) - int(tail)
   131  	if size < 0 {
   132  		size *= -1
   133  	}
   134  
   135  	return size
   136  }