@@ -289,66 +289,82 @@ func (l *outStreamList) dequeue() *outStream {
289
289
}
290
290
291
291
// controlBuffer is a way to pass information to loopy.
292
- // Information is passed as specific struct types called control frames.
293
- // A control frame not only represents data, messages or headers to be sent out
294
- // but can also be used to instruct loopy to update its internal state.
295
- // It shouldn't be confused with an HTTP2 frame, although some of the control frames
296
- // like dataFrame and headerFrame do go out on wire as HTTP2 frames.
292
+ //
293
+ // Information is passed as specific struct types called control frames. A
294
+ // control frame not only represents data, messages or headers to be sent out
295
+ // but can also be used to instruct loopy to update its internal state. It
296
+ // shouldn't be confused with an HTTP2 frame, although some of the control
297
+ // frames like dataFrame and headerFrame do go out on wire as HTTP2 frames.
297
298
type controlBuffer struct {
298
- ch chan struct {}
299
- done <- chan struct {}
299
+ wakeupCh chan struct {} // Unblocks readers waiting for something to read.
300
+ done <- chan struct {} // Closed when the transport is done.
301
+
302
+ // Mutex guards all the fields below, except trfChan which can be read
303
+ // atomically without holding mu.
300
304
mu sync.Mutex
301
- consumerWaiting bool
302
- list * itemList
303
- err error
305
+ consumerWaiting bool // True when readers are blocked waiting for new data.
306
+ closed bool // True when the controlbuf is finished.
307
+ list * itemList // List of queued control frames.
304
308
305
309
// transportResponseFrames counts the number of queued items that represent
306
310
// the response of an action initiated by the peer. trfChan is created
307
311
// when transportResponseFrames >= maxQueuedTransportResponseFrames and is
308
312
// closed and nilled when transportResponseFrames drops below the
309
313
// threshold. Both fields are protected by mu.
310
314
transportResponseFrames int
311
- trfChan atomic.Value // chan struct{}
315
+ trfChan atomic.Pointer [ chan struct {}]
312
316
}
313
317
314
318
func newControlBuffer (done <- chan struct {}) * controlBuffer {
315
319
return & controlBuffer {
316
- ch : make (chan struct {}, 1 ),
317
- list : & itemList {},
318
- done : done ,
320
+ wakeupCh : make (chan struct {}, 1 ),
321
+ list : & itemList {},
322
+ done : done ,
319
323
}
320
324
}
321
325
322
- // throttle blocks if there are too many incomingSettings/cleanupStreams in the
323
- // controlbuf.
326
+ // throttle blocks if there are too many frames in the control buf that
327
+ // represent the response of an action initiated by the peer, like
328
+ // incomingSettings cleanupStreams etc.
324
329
func (c * controlBuffer ) throttle () {
325
- ch , _ := c .trfChan .Load ().(chan struct {})
326
- if ch != nil {
330
+ if ch := c .trfChan .Load (); ch != nil {
327
331
select {
328
- case <- ch :
332
+ case <- ( * ch ) :
329
333
case <- c .done :
330
334
}
331
335
}
332
336
}
333
337
338
+ // put adds an item to the controlbuf.
334
339
func (c * controlBuffer ) put (it cbItem ) error {
335
340
_ , err := c .executeAndPut (nil , it )
336
341
return err
337
342
}
338
343
344
+ // executeAndPut runs f, and if the return value is true, adds the given item to
345
+ // the controlbuf. The item could be nil, in which case, this method simply
346
+ // executes f and does not add the item to the controlbuf.
347
+ //
348
+ // The first return value indicates whether the item was successfully added to
349
+ // the control buffer. A non-nil error, specifically ErrConnClosing, is returned
350
+ // if the control buffer is already closed.
339
351
func (c * controlBuffer ) executeAndPut (f func () bool , it cbItem ) (bool , error ) {
340
- var wakeUp bool
341
352
c .mu .Lock ()
342
- if c .err != nil {
343
- c .mu .Unlock ()
344
- return false , c .err
353
+ defer c .mu .Unlock ()
354
+
355
+ if c .closed {
356
+ return false , ErrConnClosing
345
357
}
346
358
if f != nil {
347
359
if ! f () { // f wasn't successful
348
- c .mu .Unlock ()
349
360
return false , nil
350
361
}
351
362
}
363
+ if it == nil {
364
+ return true , nil
365
+ }
366
+
367
+ var wakeUp bool
352
368
if c .consumerWaiting {
353
369
wakeUp = true
354
370
c .consumerWaiting = false
@@ -359,77 +375,81 @@ func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) {
359
375
if c .transportResponseFrames == maxQueuedTransportResponseFrames {
360
376
// We are adding the frame that puts us over the threshold; create
361
377
// a throttling channel.
362
- c .trfChan .Store (make (chan struct {}))
378
+ ch := make (chan struct {})
379
+ c .trfChan .Store (& ch )
363
380
}
364
381
}
365
- c .mu .Unlock ()
366
382
if wakeUp {
367
383
select {
368
- case c .ch <- struct {}{}:
384
+ case c .wakeupCh <- struct {}{}:
369
385
default :
370
386
}
371
387
}
372
388
return true , nil
373
389
}
374
390
375
- // Note argument f should never be nil.
376
- func (c * controlBuffer ) execute (f func (it any ) bool , it any ) (bool , error ) {
377
- c .mu .Lock ()
378
- if c .err != nil {
379
- c .mu .Unlock ()
380
- return false , c .err
381
- }
382
- if ! f (it ) { // f wasn't successful
383
- c .mu .Unlock ()
384
- return false , nil
385
- }
386
- c .mu .Unlock ()
387
- return true , nil
388
- }
389
-
391
+ // get returns the next control frame from the control buffer. If block is true
392
+ // **and** there are no control frames in the control buffer, the call blocks
393
+ // until one of the conditions is met: there is a frame to return or the
394
+ // transport is closed.
390
395
func (c * controlBuffer ) get (block bool ) (any , error ) {
391
396
for {
392
397
c .mu .Lock ()
393
- if c .err != nil {
394
- c .mu .Unlock ()
395
- return nil , c .err
396
- }
397
- if ! c .list .isEmpty () {
398
- h := c .list .dequeue ().(cbItem )
399
- if h .isTransportResponseFrame () {
400
- if c .transportResponseFrames == maxQueuedTransportResponseFrames {
401
- // We are removing the frame that put us over the
402
- // threshold; close and clear the throttling channel.
403
- ch := c .trfChan .Load ().(chan struct {})
404
- close (ch )
405
- c .trfChan .Store ((chan struct {})(nil ))
406
- }
407
- c .transportResponseFrames --
408
- }
398
+ frame , err := c .getOnceLocked ()
399
+ if frame != nil || err != nil || ! block {
400
+ // If we read a frame or an error, we can return to the caller. The
401
+ // call to getOnceLocked() returns a nil frame and a nil error if
402
+ // there is nothing to read, and in that case, if the caller asked
403
+ // us not to block, we can return now as well.
409
404
c .mu .Unlock ()
410
- return h , nil
411
- }
412
- if ! block {
413
- c .mu .Unlock ()
414
- return nil , nil
405
+ return frame , err
415
406
}
416
407
c .consumerWaiting = true
417
408
c .mu .Unlock ()
409
+
410
+ // Release the lock above and wait to be woken up.
418
411
select {
419
- case <- c .ch :
412
+ case <- c .wakeupCh :
420
413
case <- c .done :
421
414
return nil , errors .New ("transport closed by client" )
422
415
}
423
416
}
424
417
}
425
418
419
+ // Callers must not use this method, but should instead use get().
420
+ //
421
+ // Caller must hold c.mu.
422
+ func (c * controlBuffer ) getOnceLocked () (any , error ) {
423
+ if c .closed {
424
+ return false , ErrConnClosing
425
+ }
426
+ if c .list .isEmpty () {
427
+ return nil , nil
428
+ }
429
+ h := c .list .dequeue ().(cbItem )
430
+ if h .isTransportResponseFrame () {
431
+ if c .transportResponseFrames == maxQueuedTransportResponseFrames {
432
+ // We are removing the frame that put us over the
433
+ // threshold; close and clear the throttling channel.
434
+ ch := c .trfChan .Swap (nil )
435
+ close (* ch )
436
+ }
437
+ c .transportResponseFrames --
438
+ }
439
+ return h , nil
440
+ }
441
+
442
+ // finish closes the control buffer, cleaning up any streams that have queued
443
+ // header frames. Once this method returns, no more frames can be added to the
444
+ // control buffer, and attempts to do so will return ErrConnClosing.
426
445
func (c * controlBuffer ) finish () {
427
446
c .mu .Lock ()
428
- if c .err != nil {
429
- c .mu .Unlock ()
447
+ defer c .mu .Unlock ()
448
+
449
+ if c .closed {
430
450
return
431
451
}
432
- c .err = ErrConnClosing
452
+ c .closed = true
433
453
// There may be headers for streams in the control buffer.
434
454
// These streams need to be cleaned out since the transport
435
455
// is still not aware of these yet.
@@ -442,15 +462,14 @@ func (c *controlBuffer) finish() {
442
462
hdr .onOrphaned (ErrConnClosing )
443
463
}
444
464
}
465
+
445
466
// In case throttle() is currently in flight, it needs to be unblocked.
446
467
// Otherwise, the transport may not close, since the transport is closed by
447
468
// the reader encountering the connection error.
448
- ch , _ := c .trfChan .Load ().( chan struct {} )
469
+ ch := c .trfChan .Swap ( nil )
449
470
if ch != nil {
450
- close (ch )
471
+ close (* ch )
451
472
}
452
- c .trfChan .Store ((chan struct {})(nil ))
453
- c .mu .Unlock ()
454
473
}
455
474
456
475
type side int
0 commit comments