github.com/rudderlabs/rudder-go-kit@v0.30.0/async/single_sender.go (about)

     1  package async
     2  
     3  import (
     4  	"context"
     5  )
     6  
     7  // SingleSender is a helper for sending and receiving values to and from a channel between 2 separate goroutines, a sending and a receiving goroutine, while at the same time supporting the following scenarios:
     8  //  1. The sending goroutine in case the parent context is canceled should be able to notify the receiver goroutine about the error through the channel.
     9  //  2. The receiving goroutine should be able to stop listening from the channel (a.k.a. leave) at any point.
    10  //  3. The sending goroutine shouldn't be blocked trying to send to the channel when the receiver has left it.
    11  //  4. Receiver's departure should act as a context cancellation signal to the sending goroutine, i.e. it should stop working.
    12  type SingleSender[T any] struct {
    13  	ctx           context.Context
    14  	ctxCancel     context.CancelFunc
    15  	sendCtx       context.Context
    16  	sendCtxCancel context.CancelFunc
    17  	ch            chan T
    18  	closed        bool
    19  }
    20  
    21  // Begin creates a new channel and returns it along with a context for the sending goroutine to use and a function for the receiving goroutine to be able to leave the "conversation" if needed.
    22  func (s *SingleSender[T]) Begin(parentCtx context.Context) (ctx context.Context, ch <-chan T, leave func()) {
    23  	s.ctx, s.ctxCancel = context.WithCancel(parentCtx)
    24  	s.ch = make(chan T)
    25  	s.sendCtx, s.sendCtxCancel = context.WithCancel(context.Background())
    26  	return s.ctx, s.ch, s.sendCtxCancel
    27  }
    28  
    29  // Send tries to send a value to the channel. If the channel is closed, or the receiving goroutine has left it does nothing.
    30  func (s *SingleSender[T]) Send(value T) {
    31  	closed := s.closed
    32  	if closed { // don't send to a closed channel
    33  		return
    34  	}
    35  	select {
    36  	case <-s.sendCtx.Done():
    37  		s.ctxCancel()
    38  		return
    39  	case s.ch <- value:
    40  	}
    41  }
    42  
    43  // Close the channel and cancel all related contexts.
    44  func (s *SingleSender[T]) Close() {
    45  	if s.closed {
    46  		return
    47  	}
    48  	s.closed = true
    49  	s.ctxCancel()
    50  	s.sendCtxCancel()
    51  	close(s.ch)
    52  }