github.com/ooni/psiphon/tunnel-core@v0.0.0-20230105123940-fe12a24c96ee/oovendor/quic-go/send_queue.go (about)

     1  package quic
     2  
     3  type sender interface {
     4  	Send(p *packetBuffer)
     5  	Run() error
     6  	WouldBlock() bool
     7  	Available() <-chan struct{}
     8  	Close()
     9  }
    10  
    11  type sendQueue struct {
    12  	queue       chan *packetBuffer
    13  	closeCalled chan struct{} // runStopped when Close() is called
    14  	runStopped  chan struct{} // runStopped when the run loop returns
    15  	available   chan struct{}
    16  	conn        sendConn
    17  }
    18  
    19  var _ sender = &sendQueue{}
    20  
    21  const sendQueueCapacity = 8
    22  
    23  func newSendQueue(conn sendConn) sender {
    24  	return &sendQueue{
    25  		conn:        conn,
    26  		runStopped:  make(chan struct{}),
    27  		closeCalled: make(chan struct{}),
    28  		available:   make(chan struct{}, 1),
    29  		queue:       make(chan *packetBuffer, sendQueueCapacity),
    30  	}
    31  }
    32  
    33  // Send sends out a packet. It's guaranteed to not block.
    34  // Callers need to make sure that there's actually space in the send queue by calling WouldBlock.
    35  // Otherwise Send will panic.
    36  func (h *sendQueue) Send(p *packetBuffer) {
    37  	select {
    38  	case h.queue <- p:
    39  	case <-h.runStopped:
    40  	default:
    41  		panic("sendQueue.Send would have blocked")
    42  	}
    43  }
    44  
    45  func (h *sendQueue) WouldBlock() bool {
    46  	return len(h.queue) == sendQueueCapacity
    47  }
    48  
    49  func (h *sendQueue) Available() <-chan struct{} {
    50  	return h.available
    51  }
    52  
    53  func (h *sendQueue) Run() error {
    54  	defer close(h.runStopped)
    55  	var shouldClose bool
    56  	for {
    57  		if shouldClose && len(h.queue) == 0 {
    58  			return nil
    59  		}
    60  		select {
    61  		case <-h.closeCalled:
    62  			h.closeCalled = nil // prevent this case from being selected again
    63  			// make sure that all queued packets are actually sent out
    64  			shouldClose = true
    65  		case p := <-h.queue:
    66  			if err := h.conn.Write(p.Data); err != nil {
    67  				// This additional check enables:
    68  				// 1. Checking for "datagram too large" message from the kernel, as such,
    69  				// 2. Path MTU discovery,and
    70  				// 3. Eventual detection of loss PingFrame.
    71  				if !isMsgSizeErr(err) {
    72  					return err
    73  				}
    74  			}
    75  			p.Release()
    76  			select {
    77  			case h.available <- struct{}{}:
    78  			default:
    79  			}
    80  		}
    81  	}
    82  }
    83  
    84  func (h *sendQueue) Close() {
    85  	close(h.closeCalled)
    86  	// wait until the run loop returned
    87  	<-h.runStopped
    88  }