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 }