@@ -255,8 +255,20 @@ type lazyRespSet struct {
255
255
frameTimeout time.Duration
256
256
257
257
// Internal bookkeeping.
258
- dataOrFinishEvent * sync.Cond
259
- bufferedResponses []* storepb.SeriesResponse
258
+ dataOrFinishEvent * sync.Cond
259
+ // This event firing means the buffer has a slot for more data.
260
+ bufferSlotEvent * sync.Cond
261
+ fixedBufferSize int
262
+ // This a ring buffer of size fixedBufferSize.
263
+ // A ring buffer of size N can hold N - 1 elements at most in order to distinguish being empty from being full.
264
+ bufferedResponses []* storepb.SeriesResponse
265
+ // ringHead points to the first element in the ring buffer.
266
+ // ringTail points to the slot after the last element in the ring buffer.
267
+ // if ringHead == ringTail then the buffer is empty.
268
+ // if ringHead == (ringTail + 1) % fixedBufferSize then the buffer is full.
269
+ ringHead int
270
+ ringTail int
271
+ closed bool
260
272
bufferedResponsesMtx * sync.Mutex
261
273
lastResp * storepb.SeriesResponse
262
274
@@ -266,24 +278,32 @@ type lazyRespSet struct {
266
278
shardMatcher * storepb.ShardMatcher
267
279
}
268
280
281
+ func (l * lazyRespSet ) isEmpty () bool {
282
+ return l .ringHead == l .ringTail
283
+ }
284
+
285
+ func (l * lazyRespSet ) isFull () bool {
286
+ return (l .ringTail + 1 )% l .fixedBufferSize == l .ringHead
287
+ }
288
+
269
289
func (l * lazyRespSet ) Empty () bool {
270
290
l .bufferedResponsesMtx .Lock ()
271
291
defer l .bufferedResponsesMtx .Unlock ()
272
292
273
293
// NOTE(GiedriusS): need to wait here for at least one
274
294
// response so that we could build the heap properly.
275
- if l .noMoreData && len ( l . bufferedResponses ) == 0 {
295
+ if l .noMoreData && l . isEmpty () {
276
296
return true
277
297
}
278
298
279
- for len ( l . bufferedResponses ) == 0 {
299
+ for l . isEmpty () {
280
300
l .dataOrFinishEvent .Wait ()
281
- if l .noMoreData && len ( l . bufferedResponses ) == 0 {
301
+ if l .noMoreData && l . isEmpty () {
282
302
break
283
303
}
284
304
}
285
305
286
- return len ( l . bufferedResponses ) == 0 && l .noMoreData
306
+ return l . isEmpty () && l .noMoreData
287
307
}
288
308
289
309
// Next either blocks until more data is available or reads
@@ -295,23 +315,24 @@ func (l *lazyRespSet) Next() bool {
295
315
296
316
l .initialized = true
297
317
298
- if l .noMoreData && len ( l . bufferedResponses ) == 0 {
318
+ if l .noMoreData && l . isEmpty () {
299
319
l .lastResp = nil
300
320
301
321
return false
302
322
}
303
323
304
- for len ( l . bufferedResponses ) == 0 {
324
+ for l . isEmpty () {
305
325
l .dataOrFinishEvent .Wait ()
306
- if l .noMoreData && len ( l . bufferedResponses ) == 0 {
326
+ if l .noMoreData && l . isEmpty () {
307
327
break
308
328
}
309
329
}
310
330
311
- if len ( l . bufferedResponses ) > 0 {
312
- l .lastResp = l .bufferedResponses [0 ]
331
+ if ! l . isEmpty () {
332
+ l .lastResp = l .bufferedResponses [l . ringHead ]
313
333
if l .initialized {
314
- l .bufferedResponses = l .bufferedResponses [1 :]
334
+ l .ringHead = (l .ringHead + 1 ) % l .fixedBufferSize
335
+ l .bufferSlotEvent .Signal ()
315
336
}
316
337
return true
317
338
}
@@ -338,8 +359,12 @@ func newLazyRespSet(
338
359
shardMatcher * storepb.ShardMatcher ,
339
360
applySharding bool ,
340
361
emptyStreamResponses prometheus.Counter ,
362
+ fixedBufferSize int ,
341
363
) respSet {
342
- bufferedResponses := []* storepb.SeriesResponse {}
364
+ // A ring buffer of size N can hold N - 1 elements at most in order to distinguish being empty from being full.
365
+ // That's why the size is increased by 1 internally.
366
+ fixedBufferSize ++
367
+ bufferedResponses := make ([]* storepb.SeriesResponse , fixedBufferSize )
343
368
bufferedResponsesMtx := & sync.Mutex {}
344
369
dataAvailable := sync .NewCond (bufferedResponsesMtx )
345
370
@@ -351,9 +376,14 @@ func newLazyRespSet(
351
376
closeSeries : closeSeries ,
352
377
span : span ,
353
378
dataOrFinishEvent : dataAvailable ,
379
+ bufferSlotEvent : sync .NewCond (bufferedResponsesMtx ),
354
380
bufferedResponsesMtx : bufferedResponsesMtx ,
355
381
bufferedResponses : bufferedResponses ,
356
382
shardMatcher : shardMatcher ,
383
+ fixedBufferSize : fixedBufferSize ,
384
+ ringHead : 0 ,
385
+ ringTail : 0 ,
386
+ closed : false ,
357
387
}
358
388
respSet .storeLabels = make (map [string ]struct {})
359
389
for _ , ls := range storeLabelSets {
@@ -406,11 +436,16 @@ func newLazyRespSet(
406
436
} else {
407
437
rerr = errors .Wrapf (err , "receive series from %s" , st )
408
438
}
409
-
410
439
l .span .SetTag ("err" , rerr .Error ())
411
440
412
441
l .bufferedResponsesMtx .Lock ()
413
- l .bufferedResponses = append (l .bufferedResponses , storepb .NewWarnSeriesResponse (rerr ))
442
+ for l .isFull () && ! l .closed {
443
+ l .bufferSlotEvent .Wait ()
444
+ }
445
+ if ! l .closed {
446
+ l .bufferedResponses [l .ringTail ] = storepb .NewWarnSeriesResponse (rerr )
447
+ l .ringTail = (l .ringTail + 1 ) % l .fixedBufferSize
448
+ }
414
449
l .noMoreData = true
415
450
l .dataOrFinishEvent .Signal ()
416
451
l .bufferedResponsesMtx .Unlock ()
@@ -429,8 +464,14 @@ func newLazyRespSet(
429
464
}
430
465
431
466
l .bufferedResponsesMtx .Lock ()
432
- l .bufferedResponses = append (l .bufferedResponses , resp )
433
- l .dataOrFinishEvent .Signal ()
467
+ for l .isFull () && ! l .closed {
468
+ l .bufferSlotEvent .Wait ()
469
+ }
470
+ if ! l .closed {
471
+ l .bufferedResponses [l .ringTail ] = resp
472
+ l .ringTail = (l .ringTail + 1 ) % l .fixedBufferSize
473
+ l .dataOrFinishEvent .Signal ()
474
+ }
434
475
l .bufferedResponsesMtx .Unlock ()
435
476
return true
436
477
}
@@ -474,6 +515,7 @@ func newAsyncRespSet(
474
515
shardInfo * storepb.ShardInfo ,
475
516
logger log.Logger ,
476
517
emptyStreamResponses prometheus.Counter ,
518
+ lazyRetrievalMaxBufferedResponses int ,
477
519
) (respSet , error ) {
478
520
479
521
var (
@@ -525,6 +567,11 @@ func newAsyncRespSet(
525
567
switch retrievalStrategy {
526
568
case LazyRetrieval :
527
569
span .SetTag ("retrival_strategy" , LazyRetrieval )
570
+ if lazyRetrievalMaxBufferedResponses < 1 {
571
+ // Some unit and e2e tests hit this path.
572
+ lazyRetrievalMaxBufferedResponses = 1
573
+ }
574
+
528
575
return newLazyRespSet (
529
576
span ,
530
577
frameTimeout ,
@@ -535,6 +582,7 @@ func newAsyncRespSet(
535
582
shardMatcher ,
536
583
applySharding ,
537
584
emptyStreamResponses ,
585
+ lazyRetrievalMaxBufferedResponses ,
538
586
), nil
539
587
case EagerRetrieval :
540
588
span .SetTag ("retrival_strategy" , EagerRetrieval )
@@ -560,6 +608,8 @@ func (l *lazyRespSet) Close() {
560
608
defer l .bufferedResponsesMtx .Unlock ()
561
609
562
610
l .closeSeries ()
611
+ l .closed = true
612
+ l .bufferSlotEvent .Signal ()
563
613
l .noMoreData = true
564
614
l .dataOrFinishEvent .Signal ()
565
615
0 commit comments