github.com/Jeffail/benthos/v3@v3.65.0/lib/broker/fan_out_sequential.go (about)

     1  package broker
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/Jeffail/benthos/v3/internal/component/output"
     9  	"github.com/Jeffail/benthos/v3/lib/log"
    10  	"github.com/Jeffail/benthos/v3/lib/metrics"
    11  	"github.com/Jeffail/benthos/v3/lib/response"
    12  	"github.com/Jeffail/benthos/v3/lib/types"
    13  	"github.com/Jeffail/benthos/v3/lib/util/throttle"
    14  )
    15  
    16  //------------------------------------------------------------------------------
    17  
    18  // FanOutSequential is a broker that implements types.Consumer and broadcasts
    19  // each message out to an array of outputs, but does so sequentially, only
    20  // proceeding onto an output when the preceding output has successfully
    21  // reported message receipt.
    22  type FanOutSequential struct {
    23  	logger log.Modular
    24  	stats  metrics.Type
    25  
    26  	maxInFlight  int
    27  	transactions <-chan types.Transaction
    28  
    29  	outputTSChans []chan types.Transaction
    30  	outputs       []types.Output
    31  
    32  	ctx        context.Context
    33  	close      func()
    34  	closedChan chan struct{}
    35  }
    36  
    37  // NewFanOutSequential creates a new FanOutSequential type by providing outputs.
    38  func NewFanOutSequential(
    39  	outputs []types.Output, logger log.Modular, stats metrics.Type,
    40  ) (*FanOutSequential, error) {
    41  	ctx, done := context.WithCancel(context.Background())
    42  	o := &FanOutSequential{
    43  		maxInFlight:  1,
    44  		stats:        stats,
    45  		logger:       logger,
    46  		transactions: nil,
    47  		outputs:      outputs,
    48  		closedChan:   make(chan struct{}),
    49  		ctx:          ctx,
    50  		close:        done,
    51  	}
    52  
    53  	o.outputTSChans = make([]chan types.Transaction, len(o.outputs))
    54  	for i := range o.outputTSChans {
    55  		o.outputTSChans[i] = make(chan types.Transaction)
    56  		if err := o.outputs[i].Consume(o.outputTSChans[i]); err != nil {
    57  			return nil, err
    58  		}
    59  		if mif, ok := output.GetMaxInFlight(o.outputs[i]); ok && mif > o.maxInFlight {
    60  			o.maxInFlight = mif
    61  		}
    62  	}
    63  	return o, nil
    64  }
    65  
    66  // WithMaxInFlight sets the maximum number of in-flight messages this broker
    67  // supports. This must be set before calling Consume.
    68  func (o *FanOutSequential) WithMaxInFlight(i int) *FanOutSequential {
    69  	if i < 1 {
    70  		i = 1
    71  	}
    72  	o.maxInFlight = i
    73  	return o
    74  }
    75  
    76  //------------------------------------------------------------------------------
    77  
    78  // Consume assigns a new transactions channel for the broker to read.
    79  func (o *FanOutSequential) Consume(transactions <-chan types.Transaction) error {
    80  	if o.transactions != nil {
    81  		return types.ErrAlreadyStarted
    82  	}
    83  	o.transactions = transactions
    84  
    85  	go o.loop()
    86  	return nil
    87  }
    88  
    89  // Connected returns a boolean indicating whether this output is currently
    90  // connected to its target.
    91  func (o *FanOutSequential) Connected() bool {
    92  	for _, out := range o.outputs {
    93  		if !out.Connected() {
    94  			return false
    95  		}
    96  	}
    97  	return true
    98  }
    99  
   100  // MaxInFlight returns the maximum number of in flight messages permitted by the
   101  // output. This value can be used to determine a sensible value for parent
   102  // outputs, but should not be relied upon as part of dispatcher logic.
   103  func (o *FanOutSequential) MaxInFlight() (int, bool) {
   104  	return o.maxInFlight, true
   105  }
   106  
   107  //------------------------------------------------------------------------------
   108  
   109  // loop is an internal loop that brokers incoming messages to many outputs.
   110  func (o *FanOutSequential) loop() {
   111  	var (
   112  		wg         = sync.WaitGroup{}
   113  		mMsgsRcvd  = o.stats.GetCounter("messages.received")
   114  		mOutputErr = o.stats.GetCounter("error")
   115  		mMsgsSnt   = o.stats.GetCounter("messages.sent")
   116  	)
   117  
   118  	defer func() {
   119  		wg.Wait()
   120  		for _, c := range o.outputTSChans {
   121  			close(c)
   122  		}
   123  		closeAllOutputs(o.outputs)
   124  		close(o.closedChan)
   125  	}()
   126  
   127  	sendLoop := func() {
   128  		defer wg.Done()
   129  		for {
   130  			var ts types.Transaction
   131  			var open bool
   132  
   133  			select {
   134  			case ts, open = <-o.transactions:
   135  				if !open {
   136  					return
   137  				}
   138  			case <-o.ctx.Done():
   139  				return
   140  			}
   141  			mMsgsRcvd.Incr(1)
   142  
   143  			for i := range o.outputTSChans {
   144  				msgCopy := ts.Payload.Copy()
   145  
   146  				throt := throttle.New(throttle.OptCloseChan(o.ctx.Done()))
   147  				resChan := make(chan types.Response)
   148  
   149  				// Try until success or shutdown.
   150  			sendLoop:
   151  				for {
   152  					select {
   153  					case o.outputTSChans[i] <- types.NewTransaction(msgCopy, resChan):
   154  					case <-o.ctx.Done():
   155  						return
   156  					}
   157  					select {
   158  					case res := <-resChan:
   159  						if res.Error() != nil {
   160  							o.logger.Errorf("Failed to dispatch fan out message to output '%v': %v\n", i, res.Error())
   161  							mOutputErr.Incr(1)
   162  							if !throt.Retry() {
   163  								return
   164  							}
   165  						} else {
   166  							mMsgsSnt.Incr(1)
   167  							break sendLoop
   168  						}
   169  					case <-o.ctx.Done():
   170  						return
   171  					}
   172  				}
   173  			}
   174  
   175  			select {
   176  			case ts.ResponseChan <- response.NewAck():
   177  			case <-o.ctx.Done():
   178  				return
   179  			}
   180  		}
   181  	}
   182  
   183  	// Max in flight
   184  	for i := 0; i < o.maxInFlight; i++ {
   185  		wg.Add(1)
   186  		go sendLoop()
   187  	}
   188  }
   189  
   190  // CloseAsync shuts down the FanOutSequential broker and stops processing requests.
   191  func (o *FanOutSequential) CloseAsync() {
   192  	o.close()
   193  }
   194  
   195  // WaitForClose blocks until the FanOutSequential broker has closed down.
   196  func (o *FanOutSequential) WaitForClose(timeout time.Duration) error {
   197  	select {
   198  	case <-o.closedChan:
   199  	case <-time.After(timeout):
   200  		return types.ErrTimeout
   201  	}
   202  	return nil
   203  }
   204  
   205  //------------------------------------------------------------------------------