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 }