github.com/matrixorigin/matrixone@v1.2.0/pkg/util/ring/ring.go (about) 1 // Copyright 2014 Workiva, LLC 2 // Modifications copyright (C) 2023 MatrixOrigin. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package ring 17 18 import ( 19 "runtime" 20 "sync/atomic" 21 "time" 22 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 ) 25 26 var ( 27 // ErrDisposed ring buffer is closed 28 ErrDisposed = moerr.NewInvalidStateNoCtx("ring buffer closed") 29 // ErrTimeout ring buffer timeout 30 ErrTimeout = moerr.NewInvalidStateNoCtx("ring buffer timeout") 31 ) 32 33 // roundUp takes a uint64 greater than 0 and rounds it up to the next 34 // power of 2. 35 func roundUp(v uint64) uint64 { 36 v-- 37 v |= v >> 1 38 v |= v >> 2 39 v |= v >> 4 40 v |= v >> 8 41 v |= v >> 16 42 v |= v >> 32 43 v++ 44 return v 45 } 46 47 type node[V any] struct { 48 position uint64 49 data V 50 } 51 52 type nodes[V any] []*node[V] 53 54 // RingBuffer is a MPMC buffer that achieves threadsafety with CAS operations 55 // only. A put on full or get on empty call will block until an item 56 // is put or retrieved. Calling Dispose on the RingBuffer will unblock 57 // any blocked threads with an error. This buffer is similar to the buffer 58 // described here: http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue 59 // with some minor additions. 60 type RingBuffer[V any] struct { 61 _ [8]uint64 62 queue uint64 63 _ [8]uint64 64 dequeue uint64 65 _ [8]uint64 66 mask, disposed uint64 67 _ [8]uint64 68 nodes nodes[V] 69 } 70 71 func (rb *RingBuffer[V]) init(size uint64) { 72 size = roundUp(size) 73 rb.nodes = make(nodes[V], size) 74 for i := uint64(0); i < size; i++ { 75 rb.nodes[i] = &node[V]{position: i} 76 } 77 rb.mask = size - 1 // so we don't have to do this with every put/get operation 78 } 79 80 // Reset reset the ring buffer, the call to reset needs to ensure that no concurrent 81 // reads or writes occur on the ringbuffer. 82 func (rb *RingBuffer[V]) Reset() { 83 atomic.StoreUint64(&rb.queue, 0) 84 atomic.StoreUint64(&rb.dequeue, 0) 85 86 var zero V 87 for idx, n := range rb.nodes { 88 n.data = zero 89 n.position = uint64(idx) 90 } 91 } 92 93 // Put adds the provided item to the queue. If the queue is full, this 94 // call will block until an item is added to the queue or Dispose is called 95 // on the queue. An error will be returned if the queue is disposed. 96 func (rb *RingBuffer[V]) Put(item V) error { 97 _, err := rb.put(item, false) 98 return err 99 } 100 101 // Offer adds the provided item to the queue if there is space. If the queue 102 // is full, this call will return false. An error will be returned if the 103 // queue is disposed. 104 func (rb *RingBuffer[V]) Offer(item V) (bool, error) { 105 return rb.put(item, true) 106 } 107 108 func (rb *RingBuffer[V]) put(item V, offer bool) (bool, error) { 109 var n *node[V] 110 pos := atomic.LoadUint64(&rb.queue) 111 L: 112 for { 113 if atomic.LoadUint64(&rb.disposed) == 1 { 114 return false, ErrDisposed 115 } 116 117 n = rb.nodes[pos&rb.mask] 118 seq := atomic.LoadUint64(&n.position) 119 switch dif := seq - pos; { 120 case dif == 0: 121 if atomic.CompareAndSwapUint64(&rb.queue, pos, pos+1) { 122 break L 123 } 124 default: 125 pos = atomic.LoadUint64(&rb.queue) 126 } 127 128 if offer { 129 return false, nil 130 } 131 132 runtime.Gosched() // free up the cpu before the next iteration 133 } 134 135 n.data = item 136 atomic.StoreUint64(&n.position, pos+1) 137 return true, nil 138 } 139 140 // Get will return the next item in the queue. This call will block 141 // if the queue is empty. This call will unblock when an item is added 142 // to the queue or Dispose is called on the queue. An error will be returned 143 // if the queue is disposed. 144 func (rb *RingBuffer[V]) Get() (V, bool, error) { 145 return rb.Poll(0) 146 } 147 148 // MustGet is similar to Get, but panic if returns error or not found 149 func (rb *RingBuffer[V]) MustGet() V { 150 v, ok, err := rb.Get() 151 if err != nil { 152 panic(err) 153 } 154 if !ok { 155 panic("can not get from ring buffer") 156 } 157 return v 158 } 159 160 // Poll will return the next item in the queue. This call will block 161 // if the queue is empty. This call will unblock when an item is added 162 // to the queue, Dispose is called on the queue, or the timeout is reached. An 163 // error will be returned if the queue is disposed or a timeout occurs. A 164 // non-positive timeout will block indefinitely. 165 func (rb *RingBuffer[V]) Poll(timeout time.Duration) (V, bool, error) { 166 // zero value for type parameters. 167 var zero V 168 169 var ( 170 n *node[V] 171 pos = atomic.LoadUint64(&rb.dequeue) 172 start time.Time 173 ) 174 if timeout > 0 { 175 start = time.Now() 176 } 177 L: 178 for { 179 if atomic.LoadUint64(&rb.disposed) == 1 { 180 return zero, false, ErrDisposed 181 } 182 183 n = rb.nodes[pos&rb.mask] 184 seq := atomic.LoadUint64(&n.position) 185 switch dif := seq - (pos + 1); { 186 case dif == 0: 187 if atomic.CompareAndSwapUint64(&rb.dequeue, pos, pos+1) { 188 break L 189 } 190 default: 191 pos = atomic.LoadUint64(&rb.dequeue) 192 } 193 194 if timeout > 0 && time.Since(start) >= timeout { 195 return zero, false, ErrTimeout 196 } 197 198 runtime.Gosched() // free up the cpu before the next iteration 199 } 200 data := n.data 201 n.data = zero 202 atomic.StoreUint64(&n.position, pos+rb.mask+1) 203 return data, true, nil 204 } 205 206 // Len returns the number of items in the queue. 207 func (rb *RingBuffer[V]) Len() uint64 { 208 return atomic.LoadUint64(&rb.queue) - atomic.LoadUint64(&rb.dequeue) 209 } 210 211 // Cap returns the capacity of this ring buffer. 212 func (rb *RingBuffer[V]) Cap() uint64 { 213 return uint64(len(rb.nodes)) 214 } 215 216 // Dispose will dispose of this queue and free any blocked threads 217 // in the Put and/or Get methods. Calling those methods on a disposed 218 // queue will return an error. 219 func (rb *RingBuffer[V]) Dispose() { 220 atomic.CompareAndSwapUint64(&rb.disposed, 0, 1) 221 } 222 223 // IsDisposed will return a bool indicating if this queue has been 224 // disposed. 225 func (rb *RingBuffer[V]) IsDisposed() bool { 226 return atomic.LoadUint64(&rb.disposed) == 1 227 } 228 229 // NewRingBuffer will allocate, initialize, and return a ring buffer 230 // with the specified size. 231 func NewRingBuffer[V any](size uint64) *RingBuffer[V] { 232 rb := &RingBuffer[V]{} 233 rb.init(size) 234 return rb 235 }