mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 11:42:24 -05:00
bumps uptrace/bun deps to v1.2.8 (#3698)
This commit is contained in:
parent
7b7fc528f1
commit
3617e27afa
35 changed files with 760 additions and 220 deletions
43
vendor/github.com/puzpuzpuz/xsync/v3/README.md
generated
vendored
43
vendor/github.com/puzpuzpuz/xsync/v3/README.md
generated
vendored
|
|
@ -80,7 +80,14 @@ m.Store(Point{42, 42}, 42)
|
|||
v, ok := m.Load(point{42, 42})
|
||||
```
|
||||
|
||||
Both maps use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers:
|
||||
Apart from `Range` method available for map iteration, there are also `ToPlainMap`/`ToPlainMapOf` utility functions to convert a `Map`/`MapOf` to a built-in Go's `map`:
|
||||
```go
|
||||
m := xsync.NewMapOf[int, int]()
|
||||
m.Store(42, 42)
|
||||
pm := xsync.ToPlainMapOf(m)
|
||||
```
|
||||
|
||||
Both `Map` and `MapOf` use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers:
|
||||
|
||||
```go
|
||||
m := NewMapOfWithHasher[int, int](func(i int, _ uint64) uint64 {
|
||||
|
|
@ -93,28 +100,50 @@ m := NewMapOfWithHasher[int, int](func(i int, _ uint64) uint64 {
|
|||
|
||||
When benchmarking concurrent maps, make sure to configure all of the competitors with the same hash function or, at least, take hash function performance into the consideration.
|
||||
|
||||
### SPSCQueue
|
||||
|
||||
A `SPSCQueue` is a bounded single-producer single-consumer concurrent queue. This means that not more than a single goroutine must be publishing items to the queue while not more than a single goroutine must be consuming those items.
|
||||
|
||||
```go
|
||||
q := xsync.NewSPSCQueue(1024)
|
||||
// producer inserts an item into the queue
|
||||
// optimistic insertion attempt; doesn't block
|
||||
inserted := q.TryEnqueue("bar")
|
||||
// consumer obtains an item from the queue
|
||||
// optimistic obtain attempt; doesn't block
|
||||
item, ok := q.TryDequeue() // interface{} pointing to a string
|
||||
```
|
||||
|
||||
`SPSCQueueOf[I]` is an implementation with parametrized item type. It is available for Go 1.19 or later.
|
||||
|
||||
```go
|
||||
q := xsync.NewSPSCQueueOf[string](1024)
|
||||
inserted := q.TryEnqueue("foo")
|
||||
item, ok := q.TryDequeue() // string
|
||||
```
|
||||
|
||||
The queue is based on the data structure from this [article](https://rigtorp.se/ringbuffer). The idea is to reduce the CPU cache coherency traffic by keeping cached copies of read and write indexes used by producer and consumer respectively.
|
||||
|
||||
### MPMCQueue
|
||||
|
||||
A `MPMCQueue` is a bounded multi-producer multi-consumer concurrent queue.
|
||||
|
||||
```go
|
||||
q := xsync.NewMPMCQueue(1024)
|
||||
// producer inserts an item into the queue
|
||||
q.Enqueue("foo")
|
||||
// producer optimistically inserts an item into the queue
|
||||
// optimistic insertion attempt; doesn't block
|
||||
inserted := q.TryEnqueue("bar")
|
||||
// consumer obtains an item from the queue
|
||||
item := q.Dequeue() // interface{} pointing to a string
|
||||
// optimistic obtain attempt; doesn't block
|
||||
item, ok := q.TryDequeue()
|
||||
item, ok := q.TryDequeue() // interface{} pointing to a string
|
||||
```
|
||||
|
||||
`MPMCQueueOf[I]` is an implementation with parametrized item type. It is available for Go 1.19 or later.
|
||||
|
||||
```go
|
||||
q := xsync.NewMPMCQueueOf[string](1024)
|
||||
q.Enqueue("foo")
|
||||
item := q.Dequeue() // string
|
||||
inserted := q.TryEnqueue("foo")
|
||||
item, ok := q.TryDequeue() // string
|
||||
```
|
||||
|
||||
The queue is based on the algorithm from the [MPMCQueue](https://github.com/rigtorp/MPMCQueue) C++ library which in its turn references D.Vyukov's [MPMC queue](https://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue). According to the following [classification](https://www.1024cores.net/home/lock-free-algorithms/queues), the queue is array-based, fails on overflow, provides causal FIFO, has blocking producers and consumers.
|
||||
|
|
|
|||
53
vendor/github.com/puzpuzpuz/xsync/v3/map.go
generated
vendored
53
vendor/github.com/puzpuzpuz/xsync/v3/map.go
generated
vendored
|
|
@ -200,6 +200,21 @@ func newMapTable(minTableLen int) *mapTable {
|
|||
return t
|
||||
}
|
||||
|
||||
// ToPlainMap returns a native map with a copy of xsync Map's
|
||||
// contents. The copied xsync Map should not be modified while
|
||||
// this call is made. If the copied Map is modified, the copying
|
||||
// behavior is the same as in the Range method.
|
||||
func ToPlainMap(m *Map) map[string]interface{} {
|
||||
pm := make(map[string]interface{})
|
||||
if m != nil {
|
||||
m.Range(func(key string, value interface{}) bool {
|
||||
pm[key] = value
|
||||
return true
|
||||
})
|
||||
}
|
||||
return pm
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or nil if no
|
||||
// value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
|
|
@ -279,6 +294,34 @@ func (m *Map) LoadAndStore(key string, value interface{}) (actual interface{}, l
|
|||
)
|
||||
}
|
||||
|
||||
// LoadOrTryCompute returns the existing value for the key if present.
|
||||
// Otherwise, it tries to compute the value using the provided function
|
||||
// and, if success, returns the computed value. The loaded result is true
|
||||
// if the value was loaded, false if stored. If the compute attempt was
|
||||
// cancelled, a nil will be returned.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *Map) LoadOrTryCompute(
|
||||
key string,
|
||||
valueFn func() (newValue interface{}, cancel bool),
|
||||
) (value interface{}, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(interface{}, bool) (interface{}, bool) {
|
||||
nv, c := valueFn()
|
||||
if !c {
|
||||
return nv, false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrCompute returns the existing value for the key if present.
|
||||
// Otherwise, it computes the value using the provided function and
|
||||
// returns the computed value. The loaded result is true if the value
|
||||
|
|
@ -447,11 +490,11 @@ func (m *Map) doCompute(
|
|||
if b.next == nil {
|
||||
if emptyb != nil {
|
||||
// Insertion into an existing bucket.
|
||||
var zeroedV interface{}
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
var zeroV interface{}
|
||||
newValue, del := valueFn(zeroV, false)
|
||||
if del {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
return zeroedV, false
|
||||
return zeroV, false
|
||||
}
|
||||
// First we update the value, then the key.
|
||||
// This is important for atomic snapshot states.
|
||||
|
|
@ -471,8 +514,8 @@ func (m *Map) doCompute(
|
|||
goto compute_attempt
|
||||
}
|
||||
// Insertion into a new bucket.
|
||||
var zeroedV interface{}
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
var zeroV interface{}
|
||||
newValue, del := valueFn(zeroV, false)
|
||||
if del {
|
||||
unlockBucket(&rootb.topHashMutex)
|
||||
return newValue, false
|
||||
|
|
|
|||
53
vendor/github.com/puzpuzpuz/xsync/v3/mapof.go
generated
vendored
53
vendor/github.com/puzpuzpuz/xsync/v3/mapof.go
generated
vendored
|
|
@ -149,6 +149,21 @@ func newMapOfTable[K comparable, V any](minTableLen int) *mapOfTable[K, V] {
|
|||
return t
|
||||
}
|
||||
|
||||
// ToPlainMapOf returns a native map with a copy of xsync Map's
|
||||
// contents. The copied xsync Map should not be modified while
|
||||
// this call is made. If the copied Map is modified, the copying
|
||||
// behavior is the same as in the Range method.
|
||||
func ToPlainMapOf[K comparable, V any](m *MapOf[K, V]) map[K]V {
|
||||
pm := make(map[K]V)
|
||||
if m != nil {
|
||||
m.Range(func(key K, value V) bool {
|
||||
pm[key] = value
|
||||
return true
|
||||
})
|
||||
}
|
||||
return pm
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or zero value
|
||||
// of type V if no value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
|
|
@ -243,6 +258,34 @@ func (m *MapOf[K, V]) LoadOrCompute(key K, valueFn func() V) (actual V, loaded b
|
|||
)
|
||||
}
|
||||
|
||||
// LoadOrTryCompute returns the existing value for the key if present.
|
||||
// Otherwise, it tries to compute the value using the provided function
|
||||
// and, if success, returns the computed value. The loaded result is true
|
||||
// if the value was loaded, false if stored. If the compute attempt was
|
||||
// cancelled, a zero value of type V will be returned.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *MapOf[K, V]) LoadOrTryCompute(
|
||||
key K,
|
||||
valueFn func() (newValue V, cancel bool),
|
||||
) (value V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, bool) {
|
||||
nv, c := valueFn()
|
||||
if !c {
|
||||
return nv, false
|
||||
}
|
||||
return nv, true // nv is ignored
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Compute either sets the computed new value for the key or deletes
|
||||
// the value for the key. When the delete result of the valueFn function
|
||||
// is set to true, the value will be deleted, if it exists. When delete
|
||||
|
|
@ -390,11 +433,11 @@ func (m *MapOf[K, V]) doCompute(
|
|||
if b.next == nil {
|
||||
if emptyb != nil {
|
||||
// Insertion into an existing bucket.
|
||||
var zeroedV V
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
var zeroV V
|
||||
newValue, del := valueFn(zeroV, false)
|
||||
if del {
|
||||
rootb.mu.Unlock()
|
||||
return zeroedV, false
|
||||
return zeroV, false
|
||||
}
|
||||
newe := new(entryOf[K, V])
|
||||
newe.key = key
|
||||
|
|
@ -414,8 +457,8 @@ func (m *MapOf[K, V]) doCompute(
|
|||
goto compute_attempt
|
||||
}
|
||||
// Insertion into a new bucket.
|
||||
var zeroedV V
|
||||
newValue, del := valueFn(zeroedV, false)
|
||||
var zeroV V
|
||||
newValue, del := valueFn(zeroV, false)
|
||||
if del {
|
||||
rootb.mu.Unlock()
|
||||
return newValue, false
|
||||
|
|
|
|||
56
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueue.go
generated
vendored
56
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueue.go
generated
vendored
|
|
@ -50,6 +50,8 @@ func NewMPMCQueue(capacity int) *MPMCQueue {
|
|||
|
||||
// Enqueue inserts the given item into the queue.
|
||||
// Blocks, if the queue is full.
|
||||
//
|
||||
// Deprecated: use TryEnqueue in combination with runtime.Gosched().
|
||||
func (q *MPMCQueue) Enqueue(item interface{}) {
|
||||
head := atomic.AddUint64(&q.head, 1) - 1
|
||||
slot := &q.slots[q.idx(head)]
|
||||
|
|
@ -63,6 +65,8 @@ func (q *MPMCQueue) Enqueue(item interface{}) {
|
|||
|
||||
// Dequeue retrieves and removes the item from the head of the queue.
|
||||
// Blocks, if the queue is empty.
|
||||
//
|
||||
// Deprecated: use TryDequeue in combination with runtime.Gosched().
|
||||
func (q *MPMCQueue) Dequeue() interface{} {
|
||||
tail := atomic.AddUint64(&q.tail, 1) - 1
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
|
|
@ -81,24 +85,16 @@ func (q *MPMCQueue) Dequeue() interface{} {
|
|||
// full and the item was inserted.
|
||||
func (q *MPMCQueue) TryEnqueue(item interface{}) bool {
|
||||
head := atomic.LoadUint64(&q.head)
|
||||
for {
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
if atomic.LoadUint64(&slot.turn) == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
|
||||
slot.item = item
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
prevHead := head
|
||||
head = atomic.LoadUint64(&q.head)
|
||||
if head == prevHead {
|
||||
return false
|
||||
}
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
if atomic.LoadUint64(&slot.turn) == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
|
||||
slot.item = item
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return true
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryDequeue retrieves and removes the item from the head of the
|
||||
|
|
@ -106,26 +102,18 @@ func (q *MPMCQueue) TryEnqueue(item interface{}) bool {
|
|||
// indicates that the queue isn't empty and an item was retrieved.
|
||||
func (q *MPMCQueue) TryDequeue() (item interface{}, ok bool) {
|
||||
tail := atomic.LoadUint64(&q.tail)
|
||||
for {
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
if atomic.LoadUint64(&slot.turn) == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
|
||||
item = slot.item
|
||||
ok = true
|
||||
slot.item = nil
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
prevTail := tail
|
||||
tail = atomic.LoadUint64(&q.tail)
|
||||
if tail == prevTail {
|
||||
return
|
||||
}
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
if atomic.LoadUint64(&slot.turn) == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
|
||||
item = slot.item
|
||||
ok = true
|
||||
slot.item = nil
|
||||
atomic.StoreUint64(&slot.turn, turn+1)
|
||||
return
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (q *MPMCQueue) idx(i uint64) uint64 {
|
||||
|
|
|
|||
64
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueueof.go
generated
vendored
64
vendor/github.com/puzpuzpuz/xsync/v3/mpmcqueueof.go
generated
vendored
|
|
@ -12,7 +12,7 @@ import (
|
|||
// A MPMCQueueOf is a bounded multi-producer multi-consumer concurrent
|
||||
// queue. It's a generic version of MPMCQueue.
|
||||
//
|
||||
// MPMCQueue instances must be created with NewMPMCQueueOf function.
|
||||
// MPMCQueueOf instances must be created with NewMPMCQueueOf function.
|
||||
// A MPMCQueueOf must not be copied after first use.
|
||||
//
|
||||
// Based on the data structure from the following C++ library:
|
||||
|
|
@ -61,6 +61,8 @@ func NewMPMCQueueOf[I any](capacity int) *MPMCQueueOf[I] {
|
|||
|
||||
// Enqueue inserts the given item into the queue.
|
||||
// Blocks, if the queue is full.
|
||||
//
|
||||
// Deprecated: use TryEnqueue in combination with runtime.Gosched().
|
||||
func (q *MPMCQueueOf[I]) Enqueue(item I) {
|
||||
head := atomic.AddUint64(&q.head, 1) - 1
|
||||
slot := &q.slots[q.idx(head)]
|
||||
|
|
@ -74,8 +76,10 @@ func (q *MPMCQueueOf[I]) Enqueue(item I) {
|
|||
|
||||
// Dequeue retrieves and removes the item from the head of the queue.
|
||||
// Blocks, if the queue is empty.
|
||||
//
|
||||
// Deprecated: use TryDequeue in combination with runtime.Gosched().
|
||||
func (q *MPMCQueueOf[I]) Dequeue() I {
|
||||
var zeroedI I
|
||||
var zeroI I
|
||||
tail := atomic.AddUint64(&q.tail, 1) - 1
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
|
|
@ -83,7 +87,7 @@ func (q *MPMCQueueOf[I]) Dequeue() I {
|
|||
runtime.Gosched()
|
||||
}
|
||||
item := slot.item
|
||||
slot.item = zeroedI
|
||||
slot.item = zeroI
|
||||
slot.turn.Store(turn + 1)
|
||||
return item
|
||||
}
|
||||
|
|
@ -93,24 +97,16 @@ func (q *MPMCQueueOf[I]) Dequeue() I {
|
|||
// full and the item was inserted.
|
||||
func (q *MPMCQueueOf[I]) TryEnqueue(item I) bool {
|
||||
head := atomic.LoadUint64(&q.head)
|
||||
for {
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
if slot.turn.Load() == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
|
||||
slot.item = item
|
||||
slot.turn.Store(turn + 1)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
prevHead := head
|
||||
head = atomic.LoadUint64(&q.head)
|
||||
if head == prevHead {
|
||||
return false
|
||||
}
|
||||
slot := &q.slots[q.idx(head)]
|
||||
turn := q.turn(head) * 2
|
||||
if slot.turn.Load() == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.head, head, head+1) {
|
||||
slot.item = item
|
||||
slot.turn.Store(turn + 1)
|
||||
return true
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryDequeue retrieves and removes the item from the head of the
|
||||
|
|
@ -118,27 +114,19 @@ func (q *MPMCQueueOf[I]) TryEnqueue(item I) bool {
|
|||
// indicates that the queue isn't empty and an item was retrieved.
|
||||
func (q *MPMCQueueOf[I]) TryDequeue() (item I, ok bool) {
|
||||
tail := atomic.LoadUint64(&q.tail)
|
||||
for {
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
if slot.turn.Load() == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
|
||||
var zeroedI I
|
||||
item = slot.item
|
||||
ok = true
|
||||
slot.item = zeroedI
|
||||
slot.turn.Store(turn + 1)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
prevTail := tail
|
||||
tail = atomic.LoadUint64(&q.tail)
|
||||
if tail == prevTail {
|
||||
return
|
||||
}
|
||||
slot := &q.slots[q.idx(tail)]
|
||||
turn := q.turn(tail)*2 + 1
|
||||
if slot.turn.Load() == turn {
|
||||
if atomic.CompareAndSwapUint64(&q.tail, tail, tail+1) {
|
||||
var zeroI I
|
||||
item = slot.item
|
||||
ok = true
|
||||
slot.item = zeroI
|
||||
slot.turn.Store(turn + 1)
|
||||
return
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (q *MPMCQueueOf[I]) idx(i uint64) uint64 {
|
||||
|
|
|
|||
92
vendor/github.com/puzpuzpuz/xsync/v3/spscqueue.go
generated
vendored
Normal file
92
vendor/github.com/puzpuzpuz/xsync/v3/spscqueue.go
generated
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package xsync
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A SPSCQueue is a bounded single-producer single-consumer concurrent
|
||||
// queue. This means that not more than a single goroutine must be
|
||||
// publishing items to the queue while not more than a single goroutine
|
||||
// must be consuming those items.
|
||||
//
|
||||
// SPSCQueue instances must be created with NewSPSCQueue function.
|
||||
// A SPSCQueue must not be copied after first use.
|
||||
//
|
||||
// Based on the data structure from the following article:
|
||||
// https://rigtorp.se/ringbuffer/
|
||||
type SPSCQueue struct {
|
||||
cap uint64
|
||||
pidx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad0 [cacheLineSize - 8]byte
|
||||
pcachedIdx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad1 [cacheLineSize - 8]byte
|
||||
cidx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad2 [cacheLineSize - 8]byte
|
||||
ccachedIdx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad3 [cacheLineSize - 8]byte
|
||||
items []interface{}
|
||||
}
|
||||
|
||||
// NewSPSCQueue creates a new SPSCQueue instance with the given
|
||||
// capacity.
|
||||
func NewSPSCQueue(capacity int) *SPSCQueue {
|
||||
if capacity < 1 {
|
||||
panic("capacity must be positive number")
|
||||
}
|
||||
return &SPSCQueue{
|
||||
cap: uint64(capacity + 1),
|
||||
items: make([]interface{}, capacity+1),
|
||||
}
|
||||
}
|
||||
|
||||
// TryEnqueue inserts the given item into the queue. Does not block
|
||||
// and returns immediately. The result indicates that the queue isn't
|
||||
// full and the item was inserted.
|
||||
func (q *SPSCQueue) TryEnqueue(item interface{}) bool {
|
||||
// relaxed memory order would be enough here
|
||||
idx := atomic.LoadUint64(&q.pidx)
|
||||
nextIdx := idx + 1
|
||||
if nextIdx == q.cap {
|
||||
nextIdx = 0
|
||||
}
|
||||
cachedIdx := q.ccachedIdx
|
||||
if nextIdx == cachedIdx {
|
||||
cachedIdx = atomic.LoadUint64(&q.cidx)
|
||||
q.ccachedIdx = cachedIdx
|
||||
if nextIdx == cachedIdx {
|
||||
return false
|
||||
}
|
||||
}
|
||||
q.items[idx] = item
|
||||
atomic.StoreUint64(&q.pidx, nextIdx)
|
||||
return true
|
||||
}
|
||||
|
||||
// TryDequeue retrieves and removes the item from the head of the
|
||||
// queue. Does not block and returns immediately. The ok result
|
||||
// indicates that the queue isn't empty and an item was retrieved.
|
||||
func (q *SPSCQueue) TryDequeue() (item interface{}, ok bool) {
|
||||
// relaxed memory order would be enough here
|
||||
idx := atomic.LoadUint64(&q.cidx)
|
||||
cachedIdx := q.pcachedIdx
|
||||
if idx == cachedIdx {
|
||||
cachedIdx = atomic.LoadUint64(&q.pidx)
|
||||
q.pcachedIdx = cachedIdx
|
||||
if idx == cachedIdx {
|
||||
return
|
||||
}
|
||||
}
|
||||
item = q.items[idx]
|
||||
q.items[idx] = nil
|
||||
ok = true
|
||||
nextIdx := idx + 1
|
||||
if nextIdx == q.cap {
|
||||
nextIdx = 0
|
||||
}
|
||||
atomic.StoreUint64(&q.cidx, nextIdx)
|
||||
return
|
||||
}
|
||||
96
vendor/github.com/puzpuzpuz/xsync/v3/spscqueueof.go
generated
vendored
Normal file
96
vendor/github.com/puzpuzpuz/xsync/v3/spscqueueof.go
generated
vendored
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//go:build go1.19
|
||||
// +build go1.19
|
||||
|
||||
package xsync
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A SPSCQueueOf is a bounded single-producer single-consumer concurrent
|
||||
// queue. This means that not more than a single goroutine must be
|
||||
// publishing items to the queue while not more than a single goroutine
|
||||
// must be consuming those items.
|
||||
//
|
||||
// SPSCQueueOf instances must be created with NewSPSCQueueOf function.
|
||||
// A SPSCQueueOf must not be copied after first use.
|
||||
//
|
||||
// Based on the data structure from the following article:
|
||||
// https://rigtorp.se/ringbuffer/
|
||||
type SPSCQueueOf[I any] struct {
|
||||
cap uint64
|
||||
pidx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad0 [cacheLineSize - 8]byte
|
||||
pcachedIdx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad1 [cacheLineSize - 8]byte
|
||||
cidx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad2 [cacheLineSize - 8]byte
|
||||
ccachedIdx uint64
|
||||
//lint:ignore U1000 prevents false sharing
|
||||
pad3 [cacheLineSize - 8]byte
|
||||
items []I
|
||||
}
|
||||
|
||||
// NewSPSCQueueOf creates a new SPSCQueueOf instance with the given
|
||||
// capacity.
|
||||
func NewSPSCQueueOf[I any](capacity int) *SPSCQueueOf[I] {
|
||||
if capacity < 1 {
|
||||
panic("capacity must be positive number")
|
||||
}
|
||||
return &SPSCQueueOf[I]{
|
||||
cap: uint64(capacity + 1),
|
||||
items: make([]I, capacity+1),
|
||||
}
|
||||
}
|
||||
|
||||
// TryEnqueue inserts the given item into the queue. Does not block
|
||||
// and returns immediately. The result indicates that the queue isn't
|
||||
// full and the item was inserted.
|
||||
func (q *SPSCQueueOf[I]) TryEnqueue(item I) bool {
|
||||
// relaxed memory order would be enough here
|
||||
idx := atomic.LoadUint64(&q.pidx)
|
||||
next_idx := idx + 1
|
||||
if next_idx == q.cap {
|
||||
next_idx = 0
|
||||
}
|
||||
cached_idx := q.ccachedIdx
|
||||
if next_idx == cached_idx {
|
||||
cached_idx = atomic.LoadUint64(&q.cidx)
|
||||
q.ccachedIdx = cached_idx
|
||||
if next_idx == cached_idx {
|
||||
return false
|
||||
}
|
||||
}
|
||||
q.items[idx] = item
|
||||
atomic.StoreUint64(&q.pidx, next_idx)
|
||||
return true
|
||||
}
|
||||
|
||||
// TryDequeue retrieves and removes the item from the head of the
|
||||
// queue. Does not block and returns immediately. The ok result
|
||||
// indicates that the queue isn't empty and an item was retrieved.
|
||||
func (q *SPSCQueueOf[I]) TryDequeue() (item I, ok bool) {
|
||||
// relaxed memory order would be enough here
|
||||
idx := atomic.LoadUint64(&q.cidx)
|
||||
cached_idx := q.pcachedIdx
|
||||
if idx == cached_idx {
|
||||
cached_idx = atomic.LoadUint64(&q.pidx)
|
||||
q.pcachedIdx = cached_idx
|
||||
if idx == cached_idx {
|
||||
return
|
||||
}
|
||||
}
|
||||
var zeroI I
|
||||
item = q.items[idx]
|
||||
q.items[idx] = zeroI
|
||||
ok = true
|
||||
next_idx := idx + 1
|
||||
if next_idx == q.cap {
|
||||
next_idx = 0
|
||||
}
|
||||
atomic.StoreUint64(&q.cidx, next_idx)
|
||||
return
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue