github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/globalclockupdater/worker.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package globalclockupdater 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/errors" 11 "gopkg.in/juju/worker.v1" 12 "gopkg.in/tomb.v2" 13 14 "github.com/juju/juju/core/globalclock" 15 ) 16 17 // Logger defines the methods we use from loggo.Logger. 18 type Logger interface { 19 Tracef(string, ...interface{}) 20 Warningf(string, ...interface{}) 21 } 22 23 // Config contains the configuration for the global clock updater worker. 24 type Config struct { 25 // NewUpdater returns a new global clock updater. 26 NewUpdater func() (globalclock.Updater, error) 27 28 // LocalClock is the local wall clock. The times returned must 29 // contain a monotonic component (Go 1.9+). 30 LocalClock clock.Clock 31 32 // UpdateInterval is the amount of time in between clock updates. 33 UpdateInterval time.Duration 34 35 // BackoffDelay is the amount of time to delay before attempting 36 // another update when a concurrent write is detected. 37 BackoffDelay time.Duration 38 39 // Logger determines where we write log messages. 40 Logger Logger 41 } 42 43 // Validate validates the configuration. 44 func (config Config) Validate() error { 45 if config.NewUpdater == nil { 46 return errors.NotValidf("nil NewUpdater") 47 } 48 if config.LocalClock == nil { 49 return errors.NotValidf("nil LocalClock") 50 } 51 if config.UpdateInterval <= 0 { 52 return errors.NotValidf("non-positive UpdateInterval") 53 } 54 if config.BackoffDelay <= 0 { 55 return errors.NotValidf("non-positive BackoffDelay") 56 } 57 if config.Logger == nil { 58 return errors.NotValidf("nil Logger") 59 } 60 return nil 61 } 62 63 // NewWorker returns a new global clock updater worker, using the given 64 // configuration. 65 func NewWorker(config Config) (worker.Worker, error) { 66 if err := config.Validate(); err != nil { 67 return nil, errors.Annotate(err, "validating config") 68 } 69 updater, err := config.NewUpdater() 70 if err != nil { 71 return nil, errors.Annotate(err, "getting new updater") 72 } 73 w := &updaterWorker{ 74 config: config, 75 updater: updater, 76 } 77 w.tomb.Go(w.loop) 78 return w, nil 79 } 80 81 type updaterWorker struct { 82 tomb tomb.Tomb 83 config Config 84 updater globalclock.Updater 85 } 86 87 // Kill is part of the worker.Worker interface. 88 func (w *updaterWorker) Kill() { 89 w.tomb.Kill(nil) 90 } 91 92 // Wait is part of the worker.Worker interface. 93 func (w *updaterWorker) Wait() error { 94 return w.tomb.Wait() 95 } 96 97 func (w *updaterWorker) loop() error { 98 interval := w.config.UpdateInterval 99 backoff := w.config.BackoffDelay 100 101 last := w.config.LocalClock.Now() 102 timer := w.config.LocalClock.NewTimer(interval) 103 defer timer.Stop() 104 105 for { 106 select { 107 case <-w.tomb.Dying(): 108 return tomb.ErrDying 109 case <-timer.Chan(): 110 // Increment the global time by the amount of time 111 // since the moment after we initially read or last 112 // updated the clock. 113 now := w.config.LocalClock.Now() 114 amount := now.Sub(last) 115 err := w.updater.Advance(amount) 116 if globalclock.IsConcurrentUpdate(err) { 117 w.config.Logger.Tracef("concurrent update, backing off for %s", backoff) 118 last = w.config.LocalClock.Now() 119 timer.Reset(backoff) 120 continue 121 } else if globalclock.IsTimeout(err) { 122 w.config.Logger.Warningf("timed out updating clock, retrying in %s", interval) 123 timer.Reset(interval) 124 continue 125 } else if err != nil { 126 return errors.Annotate(err, "updating global clock") 127 } 128 w.config.Logger.Tracef("incremented global time by %s", interval) 129 last = w.config.LocalClock.Now() 130 timer.Reset(interval) 131 } 132 } 133 }