github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/changestream/worker.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package changestream 5 6 import ( 7 "github.com/juju/clock" 8 "github.com/juju/errors" 9 "github.com/juju/worker/v3" 10 "github.com/juju/worker/v3/catacomb" 11 12 "github.com/juju/juju/core/changestream" 13 coredatabase "github.com/juju/juju/core/database" 14 "github.com/juju/juju/worker/changestream/eventqueue" 15 "github.com/juju/juju/worker/changestream/stream" 16 "github.com/juju/juju/worker/filenotifywatcher" 17 ) 18 19 // DBGetter describes the ability to supply a sql.DB 20 // reference for a particular database. 21 type DBGetter = coredatabase.DBGetter 22 23 // FileNotifyWatcher is the interface that the worker uses to interact with the 24 // file notify watcher. 25 type FileNotifyWatcher = filenotifywatcher.FileNotifyWatcher 26 27 // FileNotifier represents a way to watch for changes in a namespace folder 28 // directory. 29 type FileNotifier interface { 30 // Changes returns a channel if a file was created or deleted. 31 Changes() (<-chan bool, error) 32 } 33 34 // ChangeStream represents an interface for getting an event queue for 35 // a particular namespace. 36 type ChangeStream interface { 37 EventQueue(string) (EventQueue, error) 38 } 39 40 // EventQueue represents an interface for managing subscriptions to listen to 41 // changes from the database change log. 42 type EventQueue interface { 43 // Subscribe returns a new subscription to listen to changes from the 44 // database change log. 45 Subscribe(...changestream.SubscriptionOption) (changestream.Subscription, error) 46 } 47 48 // EventQueueWorker represents a worker for subscribing to events from the 49 // database change log. 50 type EventQueueWorker interface { 51 worker.Worker 52 EventQueue() EventQueue 53 } 54 55 // WorkerConfig encapsulates the configuration options for the 56 // changestream worker. 57 type WorkerConfig struct { 58 DBGetter DBGetter 59 FileNotifyWatcher FileNotifyWatcher 60 Clock clock.Clock 61 Logger Logger 62 NewEventQueueWorker EventQueueWorkerFn 63 } 64 65 // Validate ensures that the config values are valid. 66 func (c *WorkerConfig) Validate() error { 67 if c.DBGetter == nil { 68 return errors.NotValidf("missing DBGetter") 69 } 70 if c.FileNotifyWatcher == nil { 71 return errors.NotValidf("missing FileNotifyWatcher") 72 } 73 if c.Clock == nil { 74 return errors.NotValidf("missing clock") 75 } 76 if c.Logger == nil { 77 return errors.NotValidf("missing logger") 78 } 79 if c.NewEventQueueWorker == nil { 80 return errors.NotValidf("missing NewEventQueueWorker") 81 } 82 return nil 83 } 84 85 type changeStreamWorker struct { 86 cfg WorkerConfig 87 catacomb catacomb.Catacomb 88 runner *worker.Runner 89 } 90 91 func newWorker(cfg WorkerConfig) (*changeStreamWorker, error) { 92 var err error 93 if err = cfg.Validate(); err != nil { 94 return nil, errors.Trace(err) 95 } 96 97 w := &changeStreamWorker{ 98 cfg: cfg, 99 runner: worker.NewRunner(worker.RunnerParams{ 100 // Prevent the runner from restarting the worker, if one of the 101 // workers dies, we want to stop the whole thing. 102 IsFatal: func(err error) bool { return false }, 103 Clock: cfg.Clock, 104 }), 105 } 106 107 if err = catacomb.Invoke(catacomb.Plan{ 108 Site: &w.catacomb, 109 Work: w.loop, 110 Init: []worker.Worker{ 111 w.runner, 112 }, 113 }); err != nil { 114 return nil, errors.Trace(err) 115 } 116 117 return w, nil 118 } 119 120 func (w *changeStreamWorker) loop() (err error) { 121 defer w.runner.Kill() 122 123 for { 124 select { 125 case <-w.catacomb.Dying(): 126 return w.catacomb.ErrDying() 127 } 128 } 129 } 130 131 // Kill is part of the worker.Worker interface. 132 func (w *changeStreamWorker) Kill() { 133 w.catacomb.Kill(nil) 134 } 135 136 // Wait is part of the worker.Worker interface. 137 func (w *changeStreamWorker) Wait() error { 138 return w.catacomb.Wait() 139 } 140 141 // EventQueue returns a new EventQueue for the given namespace. The EventQueue 142 // will be subscribed to the given options. 143 func (w *changeStreamWorker) EventQueue(namespace string) (EventQueue, error) { 144 if e, err := w.runner.Worker(namespace, w.catacomb.Dying()); err == nil { 145 return e.(EventQueueWorker).EventQueue(), nil 146 } 147 148 db, err := w.cfg.DBGetter.GetDB(namespace) 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 153 eqWorker, err := w.cfg.NewEventQueueWorker(db, fileNotifyWatcher{ 154 fileNotifier: w.cfg.FileNotifyWatcher, 155 fileName: namespace, 156 }, w.cfg.Clock, w.cfg.Logger) 157 if err != nil { 158 return nil, errors.Trace(err) 159 } 160 161 if err := w.runner.StartWorker(namespace, func() (worker.Worker, error) { 162 return eqWorker, nil 163 }); err != nil { 164 return nil, errors.Trace(err) 165 } 166 167 return eqWorker.EventQueue(), nil 168 } 169 170 // fileNotifyWatcher is a wrapper around the FileNotifyWatcher that is used to 171 // filter the events to only those that are for the given namespace. 172 type fileNotifyWatcher struct { 173 fileNotifier FileNotifyWatcher 174 fileName string 175 } 176 177 func (f fileNotifyWatcher) Changes() (<-chan bool, error) { 178 return f.fileNotifier.Changes(f.fileName) 179 } 180 181 // NewEventQueueWorker creates a new EventQueueWorker. 182 func NewEventQueueWorker(db coredatabase.TrackedDB, fileNotifier FileNotifier, clock clock.Clock, logger Logger) (EventQueueWorker, error) { 183 stream := stream.New(db, fileNotifier, clock, logger) 184 185 eventQueue, err := eventqueue.New(stream, logger) 186 if err != nil { 187 return nil, errors.Trace(err) 188 } 189 190 w := &eventQueueWorker{ 191 eventQueue: eventQueue, 192 } 193 194 if err := catacomb.Invoke(catacomb.Plan{ 195 Site: &w.catacomb, 196 Work: w.loop, 197 Init: []worker.Worker{ 198 stream, 199 eventQueue, 200 }, 201 }); err != nil { 202 return nil, errors.Trace(err) 203 } 204 205 return w, nil 206 } 207 208 // eventQueueWorker is a worker that is responsible for managing the lifecycle 209 // of both the DBStream and the EventQueue. 210 type eventQueueWorker struct { 211 catacomb catacomb.Catacomb 212 213 eventQueue *eventqueue.EventQueue 214 } 215 216 // Kill is part of the worker.Worker interface. 217 func (w *eventQueueWorker) Kill() { 218 w.catacomb.Kill(nil) 219 } 220 221 // Wait is part of the worker.Worker interface. 222 func (w *eventQueueWorker) Wait() error { 223 return w.catacomb.Wait() 224 } 225 226 // EventQueue returns the event queue for this worker. 227 func (w *eventQueueWorker) EventQueue() EventQueue { 228 return w.eventQueue 229 } 230 231 func (w *eventQueueWorker) loop() error { 232 select { 233 case <-w.catacomb.Dying(): 234 return w.catacomb.ErrDying() 235 } 236 }