github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/globalclock/updater.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package globalclock 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 mgo "gopkg.in/mgo.v2" 12 "gopkg.in/mgo.v2/bson" 13 14 "github.com/juju/juju/core/globalclock" 15 "github.com/juju/juju/mongo" 16 ) 17 18 var ( 19 logger = loggo.GetLogger("juju.state.globalclock") 20 21 globalEpoch = time.Unix(0, 0) 22 ) 23 24 // NewUpdater returns a new Updater using the supplied config, or an error. 25 // 26 // Updaters do not need to be cleaned up themselves, but they will not function 27 // past the lifetime of their configured *mgo.Session. 28 func NewUpdater(config UpdaterConfig) (*Updater, error) { 29 if err := config.validate(); err != nil { 30 return nil, errors.Trace(err) 31 } 32 updater := &Updater{config: config} 33 t, err := updater.ensureClock() 34 if err != nil { 35 return nil, errors.Trace(err) 36 } 37 updater.time = t 38 return updater, nil 39 } 40 41 // Updater provides a means of updating the global clock time. 42 // 43 // Updater is not goroutine-safe. 44 type Updater struct { 45 config UpdaterConfig 46 time time.Time 47 } 48 49 // Advance adds the given duration to the global clock, ensuring 50 // that the clock has not been updated concurrently. 51 // 52 // Advance will return ErrConcurrentUpdate if another updater 53 // updates the clock concurrently. In this case, the updater 54 // will refresh its view of the clock, and the caller can 55 // attempt Advance later. 56 // 57 // If Advance returns any error other than ErrConcurrentUpdate, 58 // the Updater should be considered invalid, and the caller 59 // should obtain a new Updater. Failing to do so could lead 60 // to non-monotonic time, since there is no way of knowing in 61 // general whether or not the database was updated. 62 func (u *Updater) Advance(d time.Duration) error { 63 if d < 0 { 64 return errors.NotValidf("duration %s", d) 65 } 66 67 coll, closer := u.collection() 68 defer closer() 69 new := u.time.Add(d) 70 if err := coll.Update(matchTimeDoc(u.time), setTimeDoc(new)); err != nil { 71 if err == mgo.ErrNotFound { 72 // The document can only be not found if the clock 73 // was updated by another updater concurrently. We 74 // re-read the clock, and return a specific error 75 // to indicate to the user that they should try 76 // again later. 77 t, err := readClock(coll) 78 if err != nil { 79 return errors.Annotate(err, "refreshing time after write conflict") 80 } 81 u.time = t 82 return globalclock.ErrConcurrentUpdate 83 } 84 return errors.Annotatef(err, 85 "adding %s to current time %s", d, u.time, 86 ) 87 } 88 u.time = new 89 return nil 90 } 91 92 // ensureClock creates the initial epoch document if it doesn't already exist. 93 // Otherwise, the most recently written time is returned. 94 func (u *Updater) ensureClock() (time.Time, error) { 95 coll, closer := u.collection() 96 defer closer() 97 98 // Read the existing clock document if it's there, initialising 99 // it with a zero time otherwise. 100 var doc clockDoc 101 if _, err := coll.FindId(clockDocID).Apply(mgo.Change{ 102 // We can't use $set here, as otherwise we'll 103 // overwrite an existing document. 104 Update: bson.D{{"$inc", bson.D{{"time", 0}}}}, 105 Upsert: true, 106 }, &doc); err != nil { 107 return time.Time{}, errors.Annotate(err, "upserting clock document") 108 } 109 return doc.time(), nil 110 } 111 112 func (u *Updater) collection() (mongo.WriteCollection, func()) { 113 coll, closer := u.config.Mongo.GetCollection(u.config.Collection) 114 return coll.Writeable(), closer 115 } 116 117 func readClock(coll mongo.Collection) (time.Time, error) { 118 var doc clockDoc 119 if err := coll.FindId(clockDocID).One(&doc); err != nil { 120 return time.Time{}, errors.Annotate(err, "reading clock document") 121 } 122 return doc.time(), nil 123 } 124 125 // GlobalEpoch returns the global clock's epoch, an arbitrary reference time 126 // at which the global clock started. 127 func GlobalEpoch() time.Time { 128 return globalEpoch 129 }