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  }