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 //------------------------------------------------------------------------------