github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lifeflag/worker.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lifeflag 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 "github.com/juju/worker/v3/catacomb" 10 11 apilifeflag "github.com/juju/juju/api/common/lifeflag" 12 "github.com/juju/juju/core/life" 13 "github.com/juju/juju/core/watcher" 14 ) 15 16 // Facade exposes capabilities required by the worker. 17 type Facade interface { 18 Watch(names.Tag) (watcher.NotifyWatcher, error) 19 Life(names.Tag) (life.Value, error) 20 } 21 22 // Config holds the configuration and dependencies for a worker. 23 type Config struct { 24 Facade Facade 25 Entity names.Tag 26 Result life.Predicate 27 NotFoundIsDead bool 28 } 29 30 // Validate returns an error if the config cannot be expected 31 // to drive a functional worker. 32 func (config Config) Validate() error { 33 if config.Facade == nil { 34 return errors.NotValidf("nil Facade") 35 } 36 if config.Entity == nil { 37 return errors.NotValidf("nil Entity") 38 } 39 if config.Result == nil { 40 return errors.NotValidf("nil Result") 41 } 42 return nil 43 } 44 45 const ( 46 // ErrNotFound indicates that the worker cannot run because 47 // the configured entity does not exist. 48 ErrNotFound = apilifeflag.ErrEntityNotFound 49 50 // ErrValueChanged indicates that the result of Check is 51 // outdated, and the worker should be restarted. 52 ErrValueChanged = errors.ConstError("flag value changed") 53 ) 54 55 // New returns a worker that exposes the result of the configured 56 // predicate when applied to the configured entity's life value, 57 // and fails with ErrValueChanged when the result changes. 58 func New(config Config) (*Worker, error) { 59 if err := config.Validate(); err != nil { 60 return nil, errors.Trace(err) 61 } 62 63 w := &Worker{ 64 config: config, 65 } 66 plan := catacomb.Plan{ 67 Site: &w.catacomb, 68 Work: w.loop, 69 } 70 71 var err error 72 // Read it before the worker starts, so that we have a value 73 // guaranteed before we return the worker. Because we read this 74 // before we start the internal watcher, we'll need an additional 75 // read triggered by the first change event; this will *probably* 76 // be the same value, but we can't assume it. 77 w.life, err = config.Facade.Life(config.Entity) 78 if config.NotFoundIsDead && errors.Is(err, ErrNotFound) { 79 // If we handle notfound as dead, we will always be dead. 80 w.life = life.Dead 81 plan.Work = w.alwaysDead 82 } else if err != nil { 83 return nil, errors.Trace(err) 84 } 85 86 err = catacomb.Invoke(plan) 87 if err != nil { 88 return nil, errors.Trace(err) 89 } 90 return w, nil 91 } 92 93 // Worker holds the result of some predicate regarding an entity's life, 94 // and fails with ErrValueChanged when the result of the predicate changes. 95 type Worker struct { 96 catacomb catacomb.Catacomb 97 config Config 98 life life.Value 99 } 100 101 // Kill is part of the worker.Worker interface. 102 func (w *Worker) Kill() { 103 w.catacomb.Kill(nil) 104 } 105 106 // Wait is part of the worker.Worker interface. 107 func (w *Worker) Wait() error { 108 return w.catacomb.Wait() 109 } 110 111 // Check is part of the util.Flag interface. 112 func (w *Worker) Check() bool { 113 return w.config.Result(w.life) 114 } 115 116 func (w *Worker) alwaysDead() error { 117 if w.config.Result(life.Dead) != w.Check() { 118 return ErrValueChanged 119 } 120 <-w.catacomb.Dying() 121 return w.catacomb.ErrDying() 122 } 123 124 func (w *Worker) loop() error { 125 watcher, err := w.config.Facade.Watch(w.config.Entity) 126 if err != nil { 127 return errors.Trace(err) 128 } 129 if err := w.catacomb.Add(watcher); err != nil { 130 return errors.Trace(err) 131 } 132 for { 133 select { 134 case <-w.catacomb.Dying(): 135 return w.catacomb.ErrDying() 136 case <-watcher.Changes(): 137 l, err := w.config.Facade.Life(w.config.Entity) 138 if w.config.NotFoundIsDead && errors.Is(err, ErrNotFound) { 139 l = life.Dead 140 } else if err != nil { 141 return errors.Trace(err) 142 } 143 if w.config.Result(l) != w.Check() { 144 return ErrValueChanged 145 } 146 } 147 } 148 }