11package kgo
22
3- import "sync"
3+ import (
4+ "sync"
5+ )
46
5- // The ring types below are fixed sized blocking MPSC ringbuffers. These
6- // replace channels in a few places in this client. The *main* advantage they
7+ // The ring types below are based on fixed sized blocking MPSC ringbuffer.
8+ // One type is a fixed size ring, and the other type is an unlimited ring
9+ // that uses a fixed size ring if the number of elements is less than 8.
10+ //
11+ // These rings replace channels in a few places in this client. The *main* advantage they
712// provide is to allow loops that terminate.
813//
914// With channels, we always have to have a goroutine draining the channel. We
@@ -40,19 +45,19 @@ const (
4045 eight = mask7 + 1
4146)
4247
43- type ringReq struct {
48+ type fixedRing [ T any ] struct {
4449 mu sync.Mutex
4550 c * sync.Cond
4651
47- elems [eight ]promisedReq
52+ elems [eight ]T
4853
4954 head uint8
5055 tail uint8
5156 l uint8
5257 dead bool
5358}
5459
55- func (r * ringReq ) die () {
60+ func (r * fixedRing [ T ] ) die () {
5661 r .mu .Lock ()
5762 defer r .mu .Unlock ()
5863
@@ -62,7 +67,7 @@ func (r *ringReq) die() {
6267 }
6368}
6469
65- func (r * ringReq ) push (pr promisedReq ) (first , dead bool ) {
70+ func (r * fixedRing [ T ] ) push (elem T ) (first , dead bool ) {
6671 r .mu .Lock ()
6772 defer r .mu .Unlock ()
6873
@@ -77,18 +82,19 @@ func (r *ringReq) push(pr promisedReq) (first, dead bool) {
7782 return false , true
7883 }
7984
80- r .elems [r .tail ] = pr
85+ r .elems [r .tail ] = elem
8186 r .tail = (r .tail + 1 ) & mask7
8287 r .l ++
8388
8489 return r .l == 1 , false
8590}
8691
87- func (r * ringReq ) dropPeek () (next promisedReq , more , dead bool ) {
92+ func (r * fixedRing [ T ] ) dropPeek () (next T , more , dead bool ) {
8893 r .mu .Lock ()
8994 defer r .mu .Unlock ()
9095
91- r .elems [r .head ] = promisedReq {}
96+ var zero T
97+ r .elems [r .head ] = zero
9298 r .head = (r .head + 1 ) & mask7
9399 r .l --
94100
@@ -101,169 +107,88 @@ func (r *ringReq) dropPeek() (next promisedReq, more, dead bool) {
101107 return r .elems [r .head ], r .l > 0 , r .dead
102108}
103109
104- // ringResp duplicates the code above, but for promisedResp
105- type ringResp struct {
110+ // This type is slightly different because we can have overflow.
111+ // If we have overflow, we add to overflow until overflow is drained -- we
112+ // always want strict ordering.
113+ type unlimitedRing [T any ] struct {
106114 mu sync.Mutex
107- c * sync.Cond
108115
109- elems [eight ]promisedResp
116+ elems [eight ]T
110117
111118 head uint8
112119 tail uint8
113120 l uint8
114121 dead bool
122+
123+ overflow []T
115124}
116125
117- func (r * ringResp ) die () {
126+ func (r * unlimitedRing [ T ] ) die () {
118127 r .mu .Lock ()
119128 defer r .mu .Unlock ()
120129
121130 r .dead = true
122- if r .c != nil {
123- r .c .Broadcast ()
124- }
125131}
126132
127- func (r * ringResp ) push (pr promisedResp ) (first , dead bool ) {
133+ func (r * unlimitedRing [ T ] ) push (elem T ) (first , dead bool ) {
128134 r .mu .Lock ()
129135 defer r .mu .Unlock ()
130136
131- for r .l == eight && ! r .dead {
132- if r .c == nil {
133- r .c = sync .NewCond (& r .mu )
134- }
135- r .c .Wait ()
136- }
137-
138137 if r .dead {
139138 return false , true
140139 }
141140
142- r .elems [r .tail ] = pr
143- r .tail = (r .tail + 1 ) & mask7
144- r .l ++
145-
146- return r .l == 1 , false
147- }
148-
149- func (r * ringResp ) dropPeek () (next promisedResp , more , dead bool ) {
150- r .mu .Lock ()
151- defer r .mu .Unlock ()
152-
153- r .elems [r .head ] = promisedResp {}
154- r .head = (r .head + 1 ) & mask7
155- r .l --
156-
157- if r .c != nil {
158- r .c .Signal ()
159- }
160-
161- return r .elems [r .head ], r .l > 0 , r .dead
162- }
163-
164- // ringSeqResp duplicates the code above, but for *seqResp. We leave off die
165- // because we do not use it, but we keep `c` for testing lowering eight/mask7.
166- type ringSeqResp struct {
167- mu sync.Mutex
168- c * sync.Cond
169-
170- elems [eight ]* seqResp
171-
172- head uint8
173- tail uint8
174- l uint8
175- }
176-
177- func (r * ringSeqResp ) push (sr * seqResp ) (first bool ) {
178- r .mu .Lock ()
179- defer r .mu .Unlock ()
180-
181- for r .l == eight {
182- if r .c == nil {
183- r .c = sync .NewCond (& r .mu )
184- }
185- r .c .Wait ()
186- }
187-
188- r .elems [r .tail ] = sr
189- r .tail = (r .tail + 1 ) & mask7
190- r .l ++
191-
192- return r .l == 1
193- }
194-
195- func (r * ringSeqResp ) dropPeek () (next * seqResp , more bool ) {
196- r .mu .Lock ()
197- defer r .mu .Unlock ()
198-
199- r .elems [r .head ] = nil
200- r .head = (r .head + 1 ) & mask7
201- r .l --
202-
203- if r .c != nil {
204- r .c .Signal ()
205- }
206-
207- return r .elems [r .head ], r .l > 0
208- }
209-
210- // Also no die; this type is slightly different because we can have overflow.
211- // If we have overflow, we add to overflow until overflow is drained -- we
212- // always want strict odering.
213- type ringBatchPromise struct {
214- mu sync.Mutex
215-
216- elems [eight ]batchPromise
217-
218- head uint8
219- tail uint8
220- l uint8
221-
222- overflow []batchPromise
223- }
224-
225- func (r * ringBatchPromise ) push (b batchPromise ) (first bool ) {
226- r .mu .Lock ()
227- defer r .mu .Unlock ()
228-
229141 // If the ring is full, we go into overflow; if overflow is non-empty,
230142 // for ordering purposes, we add to the end of overflow. We only go
231143 // back to using the ring once overflow is finally empty.
232144 if r .l == eight || len (r .overflow ) > 0 {
233- r .overflow = append (r .overflow , b )
234- return false
145+ r .overflow = append (r .overflow , elem )
146+ return false , false
235147 }
236148
237- r .elems [r .tail ] = b
149+ r .elems [r .tail ] = elem
238150 r .tail = (r .tail + 1 ) & mask7
239151 r .l ++
240152
241- return r .l == 1
153+ return r .l == 1 , false
242154}
243155
244- func (r * ringBatchPromise ) dropPeek () (next batchPromise , more bool ) {
156+ func (r * unlimitedRing [ T ] ) dropPeek () (next T , more , dead bool ) {
245157 r .mu .Lock ()
246158 defer r .mu .Unlock ()
247159
248160 // We always drain the ring first. If the ring is ever empty, there
249161 // must be overflow: we would not be here if the ring is not-empty.
250162 if r .l > 1 {
251- r .elems [r .head ] = batchPromise {}
163+ var zero T
164+ r .elems [r .head ] = zero
252165 r .head = (r .head + 1 ) & mask7
253166 r .l --
254- return r .elems [r .head ], true
167+ return r .elems [r .head ], true , r . dead
255168 } else if r .l == 1 {
256- r .elems [r .head ] = batchPromise {}
169+ var zero T
170+ r .elems [r .head ] = zero
257171 r .head = (r .head + 1 ) & mask7
258172 r .l --
259173 if len (r .overflow ) == 0 {
260- return next , false
174+ return next , false , r . dead
261175 }
262- return r .overflow [0 ], true
176+ return r .overflow [0 ], true , r . dead
263177 }
264178 r .overflow = r .overflow [1 :]
179+
180+ // If the overflow slice is 8x the fixed ring size, and the overflow length
181+ // is less than 1/8 of the capacity, then we do shrink the overflow slice
182+ // to 2x the currently required capacity. This prevents edge cases where
183+ // the overflow slice could grow indefinitely.
184+ if cap (r .overflow ) > 64 && len (r .overflow ) < cap (r .overflow )/ 8 {
185+ shrunk := make ([]T , len (r .overflow ), len (r .overflow )* 2 )
186+ copy (shrunk , r .overflow )
187+ r .overflow = shrunk
188+ }
189+
265190 if len (r .overflow ) > 0 {
266- return r .overflow [0 ], true
191+ return r .overflow [0 ], true , r . dead
267192 }
268- return next , false
193+ return next , false , r . dead
269194}
0 commit comments