trpc.group/trpc-go/trpc-go@v1.0.3/internal/ring/ring.go (about)

     1  //
     2  //
     3  // Tencent is pleased to support the open source community by making tRPC available.
     4  //
     5  // Copyright (C) 2023 THL A29 Limited, a Tencent company.
     6  // All rights reserved.
     7  //
     8  // If you have downloaded a copy of the tRPC source code from Tencent,
     9  // please note that tRPC source code is licensed under the  Apache 2.0 License,
    10  // A copy of the Apache 2.0 License is included in this file.
    11  //
    12  //
    13  
    14  // Package ring provides a concurrent-safe circular queue, supports multiple read/write.
    15  package ring
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"runtime"
    21  	"sync/atomic"
    22  	"unsafe"
    23  
    24  	"golang.org/x/sys/cpu"
    25  )
    26  
    27  const (
    28  	// cacheLinePadSize is the size of CPU cache line.
    29  	cacheLinePadSize = unsafe.Sizeof(cpu.CacheLinePad{})
    30  )
    31  
    32  var (
    33  	// ErrQueueFull happens when the queue is full.
    34  	ErrQueueFull = errors.New("queue is full")
    35  )
    36  
    37  type ringItem[T any] struct {
    38  	putSeq uint32 // sequence number expected to put.
    39  	getSeq uint32 // sequence number expected to get.
    40  	value  T
    41  	_      [cacheLinePadSize - 8 - 16]byte
    42  }
    43  
    44  // Ring a concurrent-safe circular queue, based on the idea of Disruptor(simplified).
    45  // https://lmax-exchange.github.io/disruptor/disruptor.html
    46  type Ring[T any] struct {
    47  	capacity uint32 // the capacity of circular queue, including Empty element, must be the power of 2.
    48  	mask     uint32 // capacity mask of the circular queue.
    49  	_        [cacheLinePadSize - 8]byte
    50  	head     uint32 // the latest sequence number having been read.
    51  	_        [cacheLinePadSize - 4]byte
    52  	tail     uint32 // the latest sequence number having been written.
    53  	_        [cacheLinePadSize - 4]byte
    54  	data     []ringItem[T] // elements in the queue.
    55  	_        [cacheLinePadSize - unsafe.Sizeof([]ringItem[T]{})]byte
    56  }
    57  
    58  // New creates a circular queue.
    59  func New[T any](capacity uint32) *Ring[T] {
    60  	capacity = roundUpToPower2(capacity)
    61  	if capacity < 2 {
    62  		capacity = 2
    63  	}
    64  
    65  	r := &Ring[T]{
    66  		capacity: capacity,
    67  		mask:     capacity - 1,
    68  		data:     make([]ringItem[T], capacity),
    69  	}
    70  	// initialize every slot with read/write sequence number.
    71  	for i := range r.data {
    72  		r.data[i].getSeq = uint32(i)
    73  		r.data[i].putSeq = uint32(i)
    74  	}
    75  	// starts from Index=1 to fill the package.
    76  	r.data[0].getSeq = capacity
    77  	r.data[0].putSeq = capacity
    78  	return r
    79  }
    80  
    81  // Put puts element into the circular queue.
    82  // Directly return if the queue is full.
    83  func (r *Ring[T]) Put(val T) error {
    84  	// acquire put sequence.
    85  	seq, err := r.acquirePutSequence()
    86  	if err != nil {
    87  		return err
    88  	}
    89  	// write element.
    90  	r.commit(seq, val)
    91  	return nil
    92  }
    93  
    94  // Get gets an element from the circular queue, return element value and
    95  // the remaining number of elements.
    96  func (r *Ring[T]) Get() (T, uint32) {
    97  	// acquire get sequence.
    98  	head, size, left := r.acquireGetSequence(1)
    99  	if size == 0 {
   100  		var zero T
   101  		return zero, 0
   102  	}
   103  	// read element.
   104  	return r.consume(head), left
   105  }
   106  
   107  // Gets acquires elements from the circular queue and appends then into v,
   108  // return the number of elements acquired and remained.
   109  func (r *Ring[T]) Gets(val *[]T) (uint32, uint32) {
   110  	// batch acquire get sequence.
   111  	head, size, left := r.acquireGetSequence(uint32(cap(*val) - len(*val)))
   112  	if size == 0 {
   113  		return 0, 0
   114  	}
   115  	// batch read the elements.
   116  	for seq, i := head, uint32(0); i < size; seq, i = seq+1, i+1 {
   117  		*val = append(*val, r.consume(seq))
   118  	}
   119  	return size, left
   120  }
   121  
   122  // Cap retrieves the number of elements the circular queue can hold.
   123  func (r *Ring[T]) Cap() uint32 {
   124  	// the capacity is represented by mask.
   125  	return r.mask
   126  }
   127  
   128  // Size retrieves the number of elements in the circular queue.
   129  func (r *Ring[T]) Size() uint32 {
   130  	head := atomic.LoadUint32(&r.head)
   131  	tail := atomic.LoadUint32(&r.tail)
   132  	return r.quantity(head, tail)
   133  }
   134  
   135  // IsEmpty checks whether the queue is empty.
   136  func (r *Ring[T]) IsEmpty() bool {
   137  	head := atomic.LoadUint32(&r.head)
   138  	tail := atomic.LoadUint32(&r.tail)
   139  	return head == tail
   140  }
   141  
   142  // IsFull checks whether the queue is full.
   143  func (r *Ring[T]) IsFull() bool {
   144  	head := atomic.LoadUint32(&r.head)
   145  	next := atomic.LoadUint32(&r.tail) + 1
   146  	return next-head > r.mask
   147  }
   148  
   149  // String prints the structure of Ring.
   150  func (r *Ring[T]) String() string {
   151  	head := atomic.LoadUint32(&r.head)
   152  	tail := atomic.LoadUint32(&r.tail)
   153  	return fmt.Sprintf("Ring: Cap=%v, Head=%v, Tail=%v, Size=%v\n",
   154  		r.Cap(), head, tail, r.Size())
   155  }
   156  
   157  func (r *Ring[T]) quantity(head, tail uint32) uint32 {
   158  	return tail - head
   159  }
   160  
   161  func (r *Ring[T]) acquirePutSequence() (uint32, error) {
   162  	var tail, head, next uint32
   163  	mask := r.mask
   164  	for {
   165  		head = atomic.LoadUint32(&r.head)
   166  		tail = atomic.LoadUint32(&r.tail)
   167  		next = tail + 1
   168  		left := r.quantity(head, next)
   169  		// the queue is full, return.
   170  		if left > mask {
   171  			return 0, ErrQueueFull
   172  		}
   173  		// got the sequence number, return.
   174  		if atomic.CompareAndSwapUint32(&r.tail, tail, next) {
   175  			return next, nil
   176  		}
   177  		// fails to get the sequence number, yields the CPU
   178  		// to reduce busy loop of CPU.
   179  		runtime.Gosched()
   180  	}
   181  }
   182  
   183  func (r *Ring[T]) acquireGetSequence(ask uint32) (uint32, uint32, uint32) {
   184  	var tail, head, size uint32
   185  	for {
   186  		head = atomic.LoadUint32(&r.head)
   187  		tail = atomic.LoadUint32(&r.tail)
   188  		left := r.quantity(head, tail)
   189  		// the queue is empty, return.
   190  		if left < 1 {
   191  			return head, 0, 0
   192  		}
   193  		size = left
   194  		if ask < left {
   195  			size = ask
   196  		}
   197  		// got the sequence number, return.
   198  		if atomic.CompareAndSwapUint32(&r.head, head, head+size) {
   199  			return head + 1, size, left - size
   200  		}
   201  		// fails to get the sequence number, yields the CPU
   202  		// to reduce busy loop of CPU.
   203  		runtime.Gosched()
   204  	}
   205  }
   206  
   207  func (r *Ring[T]) commit(seq uint32, val T) {
   208  	item := &r.data[seq&r.mask]
   209  	for {
   210  		getSeq := atomic.LoadUint32(&item.getSeq)
   211  		putSeq := atomic.LoadUint32(&item.putSeq)
   212  		// Waiting for data to be ready for writing. Due to the separation of
   213  		// obtaining the right to use the sequence number and reading and writing
   214  		// data operations, there is a short period of time that the old data has
   215  		// not been read, wait for the read operation to complete and set getSeq.
   216  		if seq == putSeq && getSeq == putSeq {
   217  			break
   218  		}
   219  		runtime.Gosched()
   220  	}
   221  	// Complete the write operation and set putSeq to the next expected write sequence number.
   222  	item.value = val
   223  	atomic.AddUint32(&item.putSeq, r.capacity)
   224  }
   225  
   226  func (r *Ring[T]) consume(seq uint32) T {
   227  	item := &r.data[seq&r.mask]
   228  	for {
   229  		getSeq := atomic.LoadUint32(&item.getSeq)
   230  		putSeq := atomic.LoadUint32(&item.putSeq)
   231  		// Waiting for data to be ready to read. Due to the separation of
   232  		// obtaining the right to use the sequence number and reading and writing
   233  		// data operations, there is a short period of time that the writing data has
   234  		// not been written yet, wait for the writing operation to complete and set putSeq.
   235  		if seq == getSeq && getSeq == (putSeq-r.capacity) {
   236  			break
   237  		}
   238  		runtime.Gosched()
   239  	}
   240  	// Complete the read operation and set getSeq to the next expected read sequence number.
   241  	val := item.value
   242  	var zero T
   243  	item.value = zero
   244  	atomic.AddUint32(&item.getSeq, r.capacity)
   245  	return val
   246  }
   247  
   248  // roundUpToPower2 rounds the integer up to the Nth power of 2.
   249  func roundUpToPower2(v uint32) uint32 {
   250  	v--
   251  	v |= v >> 1
   252  	v |= v >> 2
   253  	v |= v >> 4
   254  	v |= v >> 8
   255  	v |= v >> 16
   256  	v++
   257  	return v
   258  }