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 }