github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/broker/federation/pooled_worker.go (about)

     1  // Copyright (c) 2017-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package federation
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"sync"
    12  
    13  	"github.com/choria-io/go-choria/inter"
    14  	"github.com/choria-io/go-choria/protocol"
    15  	"github.com/choria-io/go-choria/srvcache"
    16  	log "github.com/sirupsen/logrus"
    17  )
    18  
    19  type chainable interface {
    20  	Name() string
    21  	From(input chainable) error
    22  	To(output chainable) error
    23  	Input() chan chainmessage
    24  	Output() chan chainmessage
    25  }
    26  
    27  type runable interface {
    28  	Init(workers int, broker *FederationBroker) error
    29  	Run(ctx context.Context) error
    30  	Ready() bool
    31  }
    32  
    33  type chainmessage struct {
    34  	Targets   []string
    35  	RequestID string
    36  	Message   protocol.TransportMessage
    37  	Seen      []string
    38  }
    39  
    40  type pooledWorker struct {
    41  	name        string
    42  	in          chan chainmessage
    43  	out         chan chainmessage
    44  	initialized bool
    45  	broker      *FederationBroker
    46  	mode        int
    47  	capacity    int
    48  	workers     int
    49  	mu          sync.Mutex
    50  	log         *log.Entry
    51  	wg          *sync.WaitGroup
    52  
    53  	choria     inter.Framework
    54  	connection inter.ConnectionManager
    55  	servers    func() (srvcache.Servers, error)
    56  
    57  	worker func(ctx context.Context, w *pooledWorker, instance int, logger *log.Entry)
    58  }
    59  
    60  func PooledWorkerFactory(name string, workers int, mode int, capacity int, broker *FederationBroker, logger *log.Entry, worker func(context.Context, *pooledWorker, int, *log.Entry)) (*pooledWorker, error) {
    61  	w := &pooledWorker{
    62  		name:     name,
    63  		mode:     mode,
    64  		log:      logger,
    65  		worker:   worker,
    66  		capacity: capacity,
    67  		wg:       &sync.WaitGroup{},
    68  	}
    69  
    70  	err := w.Init(workers, broker)
    71  
    72  	return w, err
    73  }
    74  
    75  func (w *pooledWorker) Run(ctx context.Context) error {
    76  	w.mu.Lock()
    77  	defer w.mu.Unlock()
    78  
    79  	if !w.Ready() {
    80  		err := fmt.Errorf("could not run %s as Init() has not been called or failed", w.Name())
    81  		w.log.Warn(err)
    82  		return err
    83  	}
    84  
    85  	var err error
    86  
    87  	if w.mode != Unconnected {
    88  		switch w.mode {
    89  		case Federation:
    90  			w.servers = w.choria.FederationMiddlewareServers
    91  		case Collective:
    92  			w.servers = w.choria.MiddlewareServers
    93  		default:
    94  			err := errors.New("do not know which middleware to connect to, Mode should be one of Federation or Collective")
    95  			w.log.Error(err)
    96  			return err
    97  		}
    98  
    99  		if err != nil {
   100  			err = fmt.Errorf("could not determine middleware servers: %s", err)
   101  			w.log.Warn(err)
   102  			return err
   103  		}
   104  
   105  		srv, err := w.servers()
   106  		if err != nil {
   107  			err = fmt.Errorf("resolving initial middleware server list failed: %s", err)
   108  			w.log.Error(err)
   109  			return err
   110  		}
   111  
   112  		if srv.Count() == 0 {
   113  			err = fmt.Errorf("no middleware servers were configured for %s, cannot continue", w.name)
   114  			w.log.Error(err)
   115  			return err
   116  		}
   117  	}
   118  
   119  	for i := 0; i < w.workers; i++ {
   120  		w.wg.Add(1)
   121  
   122  		go w.worker(ctx, w, i, w.log.WithFields(log.Fields{"worker_instance": i}))
   123  	}
   124  
   125  	w.wg.Wait()
   126  
   127  	return nil
   128  }
   129  
   130  func (w *pooledWorker) Init(workers int, broker *FederationBroker) (err error) {
   131  	w.mu.Lock()
   132  	defer w.mu.Unlock()
   133  
   134  	w.workers = workers
   135  	w.choria = broker.choria
   136  	w.broker = broker
   137  
   138  	if w.mode != Unconnected {
   139  		w.connection = broker.choria
   140  	}
   141  
   142  	if w.log == nil {
   143  		w.log = broker.logger.WithFields(log.Fields{"worker": w.name})
   144  	}
   145  
   146  	if w.capacity == 0 {
   147  		w.capacity = 100
   148  	}
   149  
   150  	if w.workers == 0 {
   151  		w.workers = 2
   152  	}
   153  
   154  	w.in = make(chan chainmessage, w.capacity)
   155  	w.out = make(chan chainmessage, w.capacity)
   156  
   157  	w.initialized = true
   158  
   159  	return nil
   160  }
   161  
   162  func (w *pooledWorker) Ready() bool {
   163  	return w.initialized
   164  }
   165  
   166  func (w *pooledWorker) Name() string {
   167  	return w.name
   168  }
   169  
   170  func (w *pooledWorker) From(input chainable) error {
   171  	if input.Output() == nil {
   172  		return fmt.Errorf("Input %s does not have a output chain", input.Name())
   173  	}
   174  
   175  	w.log.Debugf("Connecting input of %s to output of %s with capacity %d", w.Name(), input.Name(), cap(input.Output()))
   176  
   177  	w.in = input.Output()
   178  
   179  	return nil
   180  }
   181  
   182  func (w *pooledWorker) To(output chainable) error {
   183  	if output.Input() == nil {
   184  		return fmt.Errorf("Output %s does not have a input chain", output.Name())
   185  	}
   186  
   187  	w.log.Debugf("Connecting output of %s to input of %s with capacity %d", w.Name(), output.Name(), cap(output.Input()))
   188  
   189  	w.out = output.Input()
   190  
   191  	return nil
   192  }
   193  
   194  func (w *pooledWorker) Input() chan chainmessage {
   195  	return w.in
   196  }
   197  
   198  func (w *pooledWorker) Output() chan chainmessage {
   199  	return w.out
   200  }