github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/periodicworker.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package worker
     5  
     6  import (
     7  	"errors"
     8  	"time"
     9  
    10  	"launchpad.net/tomb"
    11  )
    12  
    13  // ErrKilled can be returned by the PeriodicWorkerCall to signify that
    14  // the function has returned as a result of a Stop() or Kill() signal
    15  // and that the function was able to stop cleanly
    16  var ErrKilled = errors.New("worker killed")
    17  
    18  // periodicWorker implements the worker returned by NewPeriodicWorker.
    19  type periodicWorker struct {
    20  	tomb     tomb.Tomb
    21  	newTimer NewTimerFunc
    22  }
    23  
    24  // PeriodicWorkerCall represents the callable to be passed
    25  // to the periodic worker to be run every elapsed period.
    26  type PeriodicWorkerCall func(stop <-chan struct{}) error
    27  
    28  // PeriodicTimer is an interface for the timer that periodicworker
    29  // will use to handle the calls.
    30  type PeriodicTimer interface {
    31  	// Reset changes the timer to expire after duration d.
    32  	// It returns true if the timer had been active, false
    33  	// if the timer had expired or been stopped.
    34  	Reset(time.Duration) bool
    35  	// CountDown returns the channel used to signal expiration of
    36  	// the timer duration. The channel is called C in the base
    37  	// implementation of timer but the name is confusing.
    38  	CountDown() <-chan time.Time
    39  }
    40  
    41  // NewTimerFunc is a constructor used to obtain the instance
    42  // of PeriodicTimer periodicWorker uses on its loop.
    43  // TODO(fwereade): 2016-03-17 lp:1558657
    44  type NewTimerFunc func(time.Duration) PeriodicTimer
    45  
    46  // Timer implements PeriodicTimer.
    47  type Timer struct {
    48  	timer *time.Timer
    49  }
    50  
    51  // Reset implements PeriodicTimer.
    52  func (t *Timer) Reset(d time.Duration) bool {
    53  	return t.timer.Reset(d)
    54  }
    55  
    56  // CountDown implements PeriodicTimer.
    57  func (t *Timer) CountDown() <-chan time.Time {
    58  	return t.timer.C
    59  }
    60  
    61  // NewTimer is the default implementation of NewTimerFunc.
    62  func NewTimer(d time.Duration) PeriodicTimer {
    63  	return &Timer{time.NewTimer(d)}
    64  }
    65  
    66  // NewPeriodicWorker returns a worker that runs the given function continually
    67  // sleeping for sleepDuration in between each call, until Kill() is called
    68  // The stopCh argument will be closed when the worker is killed. The error returned
    69  // by the doWork function will be returned by the worker's Wait function.
    70  func NewPeriodicWorker(call PeriodicWorkerCall, period time.Duration, timerFunc NewTimerFunc) Worker {
    71  	w := &periodicWorker{newTimer: timerFunc}
    72  	go func() {
    73  		defer w.tomb.Done()
    74  		w.tomb.Kill(w.run(call, period))
    75  	}()
    76  	return w
    77  }
    78  
    79  func (w *periodicWorker) run(call PeriodicWorkerCall, period time.Duration) error {
    80  	timer := w.newTimer(0)
    81  	stop := w.tomb.Dying()
    82  	for {
    83  		select {
    84  		case <-stop:
    85  			return tomb.ErrDying
    86  		case <-timer.CountDown():
    87  			if err := call(stop); err != nil {
    88  				if err == ErrKilled {
    89  					return tomb.ErrDying
    90  				}
    91  				return err
    92  			}
    93  		}
    94  		timer.Reset(period)
    95  	}
    96  }
    97  
    98  // Kill implements Worker.Kill() and will close the channel given to the doWork
    99  // function.
   100  func (w *periodicWorker) Kill() {
   101  	w.tomb.Kill(nil)
   102  }
   103  
   104  // Wait implements Worker.Wait(), and will return the error returned by
   105  // the doWork function.
   106  func (w *periodicWorker) Wait() error {
   107  	return w.tomb.Wait()
   108  }