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  }