github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/cleaner/cleaner.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cleaner 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/errors" 11 "github.com/juju/worker/v3" 12 "github.com/juju/worker/v3/catacomb" 13 14 "github.com/juju/juju/core/watcher" 15 ) 16 17 // period is the amount of time to wait before running cleanups, 18 // since the last time they were run. It is necessary to run 19 // cleanups periodically because Cleanup will not return an 20 // error if a specific cleanup fails, and the watcher will not 21 // be triggered unless a new cleanup is added. 22 const period = 30 * time.Second 23 24 // logger is here to stop the desire of creating a package level logger. 25 // Don't do this, instead pass one through as config to the worker. 26 type logger interface{} 27 28 var _ logger = struct{}{} 29 30 type StateCleaner interface { 31 Cleanup() error 32 WatchCleanups() (watcher.NotifyWatcher, error) 33 } 34 35 // Cleaner is responsible for cleaning up the state. 36 type Cleaner struct { 37 catacomb catacomb.Catacomb 38 st StateCleaner 39 watcher watcher.NotifyWatcher 40 clock clock.Clock 41 logger Logger 42 } 43 44 // NewCleaner returns a worker.Worker that runs state.Cleanup() 45 // periodically, and whenever the CleanupWatcher signals documents 46 // marked for deletion. 47 func NewCleaner(st StateCleaner, clock clock.Clock, logger Logger) (worker.Worker, error) { 48 watcher, err := st.WatchCleanups() 49 if err != nil { 50 return nil, errors.Trace(err) 51 } 52 c := Cleaner{ 53 st: st, 54 watcher: watcher, 55 clock: clock, 56 logger: logger, 57 } 58 if err := catacomb.Invoke(catacomb.Plan{ 59 Site: &c.catacomb, 60 Work: c.loop, 61 Init: []worker.Worker{watcher}, 62 }); err != nil { 63 return nil, errors.Trace(err) 64 } 65 return &c, nil 66 } 67 68 func (c *Cleaner) loop() error { 69 timer := c.clock.NewTimer(period) 70 defer timer.Stop() 71 for { 72 select { 73 case <-c.catacomb.Dying(): 74 return c.catacomb.ErrDying() 75 case _, ok := <-c.watcher.Changes(): 76 if !ok { 77 return errors.New("change channel closed") 78 } 79 case <-timer.Chan(): 80 } 81 err := c.st.Cleanup() 82 if err != nil { 83 // We don't exit if a cleanup fails, we just 84 // retry after when the timer fires. This 85 // enables us to retry cleanups that fail due 86 // to a transient failure, even when there 87 // are no new cleanups added. 88 c.logger.Errorf("cannot cleanup state: %v", err) 89 } 90 timer.Reset(period) 91 } 92 } 93 94 // Kill is part of the worker.Worker interface. 95 func (c *Cleaner) Kill() { 96 c.catacomb.Kill(nil) 97 } 98 99 // Wait is part of the worker.Worker interface. 100 func (c *Cleaner) Wait() error { 101 return c.catacomb.Wait() 102 }