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  }