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