github.com/Jeffail/benthos/v3@v3.65.0/lib/pipeline/pool.go (about) 1 package pipeline 2 3 import ( 4 "runtime" 5 "sync/atomic" 6 "time" 7 8 "github.com/Jeffail/benthos/v3/internal/shutdown" 9 "github.com/Jeffail/benthos/v3/lib/log" 10 "github.com/Jeffail/benthos/v3/lib/metrics" 11 "github.com/Jeffail/benthos/v3/lib/types" 12 ) 13 14 //------------------------------------------------------------------------------ 15 16 // Pool is a pool of pipelines. Each pipeline reads from a shared transaction 17 // channel. Inputs remain coupled to their outputs as they propagate the 18 // response channel in the transaction. 19 type Pool struct { 20 running uint32 21 22 workers []types.Pipeline 23 24 log log.Modular 25 stats metrics.Type 26 27 messagesIn <-chan types.Transaction 28 messagesOut chan types.Transaction 29 30 closeChan chan struct{} 31 closed chan struct{} 32 } 33 34 // NewPool returns a new pipeline pool that utilises multiple processor threads. 35 // TODO: V4 Remove this 36 func NewPool( 37 constructor types.PipelineConstructorFunc, 38 threads int, 39 log log.Modular, 40 stats metrics.Type, 41 ) (*Pool, error) { 42 if threads <= 0 { 43 threads = runtime.NumCPU() 44 } 45 46 p := &Pool{ 47 running: 1, 48 workers: make([]types.Pipeline, threads), 49 log: log, 50 stats: stats, 51 messagesOut: make(chan types.Transaction), 52 closeChan: make(chan struct{}), 53 closed: make(chan struct{}), 54 } 55 56 for i := range p.workers { 57 procs := 0 58 var err error 59 if p.workers[i], err = constructor(&procs); err != nil { 60 return nil, err 61 } 62 } 63 64 return p, nil 65 } 66 67 func newPoolV2( 68 threads int, 69 log log.Modular, 70 stats metrics.Type, 71 msgProcessors ...types.Processor, 72 ) (*Pool, error) { 73 if threads <= 0 { 74 threads = runtime.NumCPU() 75 } 76 77 p := &Pool{ 78 running: 1, 79 workers: make([]types.Pipeline, threads), 80 log: log, 81 stats: stats, 82 messagesOut: make(chan types.Transaction), 83 closeChan: make(chan struct{}), 84 closed: make(chan struct{}), 85 } 86 87 for i := range p.workers { 88 p.workers[i] = NewProcessor(log, stats, msgProcessors...) 89 } 90 91 return p, nil 92 } 93 94 //------------------------------------------------------------------------------ 95 96 // loop is the processing loop of this pipeline. 97 func (p *Pool) loop() { 98 defer func() { 99 atomic.StoreUint32(&p.running, 0) 100 101 // Signal all workers to close. 102 for _, worker := range p.workers { 103 worker.CloseAsync() 104 } 105 106 // Wait for all workers to be closed before closing our response and 107 // messages channels as the workers may still have access to them. 108 for _, worker := range p.workers { 109 _ = worker.WaitForClose(shutdown.MaximumShutdownWait()) 110 } 111 112 close(p.messagesOut) 113 close(p.closed) 114 }() 115 116 internalMessages := make(chan types.Transaction) 117 remainingWorkers := int64(len(p.workers)) 118 119 for _, worker := range p.workers { 120 if err := worker.Consume(p.messagesIn); err != nil { 121 p.log.Errorf("Failed to start pipeline worker: %v\n", err) 122 atomic.AddInt64(&remainingWorkers, -1) 123 continue 124 } 125 go func(w types.Pipeline) { 126 defer func() { 127 if atomic.AddInt64(&remainingWorkers, -1) == 0 { 128 close(internalMessages) 129 } 130 }() 131 for { 132 var t types.Transaction 133 var open bool 134 select { 135 case t, open = <-w.TransactionChan(): 136 if !open { 137 return 138 } 139 case <-p.closeChan: 140 return 141 } 142 select { 143 case internalMessages <- t: 144 case <-p.closeChan: 145 return 146 } 147 } 148 }(worker) 149 } 150 151 for atomic.LoadUint32(&p.running) == 1 && atomic.LoadInt64(&remainingWorkers) > 0 { 152 select { 153 case t, open := <-internalMessages: 154 if !open { 155 return 156 } 157 select { 158 case p.messagesOut <- t: 159 case <-p.closeChan: 160 return 161 } 162 case <-p.closeChan: 163 return 164 } 165 } 166 } 167 168 //------------------------------------------------------------------------------ 169 170 // Consume assigns a messages channel for the pipeline to read. 171 func (p *Pool) Consume(msgs <-chan types.Transaction) error { 172 if p.messagesIn != nil { 173 return types.ErrAlreadyStarted 174 } 175 p.messagesIn = msgs 176 go p.loop() 177 return nil 178 } 179 180 // TransactionChan returns the channel used for consuming messages from this 181 // pipeline. 182 func (p *Pool) TransactionChan() <-chan types.Transaction { 183 return p.messagesOut 184 } 185 186 // CloseAsync shuts down the pipeline and stops processing messages. 187 func (p *Pool) CloseAsync() { 188 if atomic.CompareAndSwapUint32(&p.running, 1, 0) { 189 close(p.closeChan) 190 } 191 } 192 193 // WaitForClose - Blocks until the StackBuffer output has closed down. 194 func (p *Pool) WaitForClose(timeout time.Duration) error { 195 select { 196 case <-p.closed: 197 case <-time.After(timeout): 198 return types.ErrTimeout 199 } 200 return nil 201 } 202 203 //------------------------------------------------------------------------------