ticker.go raw

   1  package backoff
   2  
   3  import (
   4  	"sync"
   5  	"time"
   6  )
   7  
   8  // Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
   9  //
  10  // Ticks will continue to arrive when the previous operation is still running,
  11  // so operations that take a while to fail could run in quick succession.
  12  type Ticker struct {
  13  	C        <-chan time.Time
  14  	c        chan time.Time
  15  	b        BackOff
  16  	timer    timer
  17  	stop     chan struct{}
  18  	stopOnce sync.Once
  19  }
  20  
  21  // NewTicker returns a new Ticker containing a channel that will send
  22  // the time at times specified by the BackOff argument. Ticker is
  23  // guaranteed to tick at least once.  The channel is closed when Stop
  24  // method is called or BackOff stops. It is not safe to manipulate the
  25  // provided backoff policy (notably calling NextBackOff or Reset)
  26  // while the ticker is running.
  27  func NewTicker(b BackOff) *Ticker {
  28  	c := make(chan time.Time)
  29  	t := &Ticker{
  30  		C:     c,
  31  		c:     c,
  32  		b:     b,
  33  		timer: &defaultTimer{},
  34  		stop:  make(chan struct{}),
  35  	}
  36  	t.b.Reset()
  37  	go t.run()
  38  	return t
  39  }
  40  
  41  // Stop turns off a ticker. After Stop, no more ticks will be sent.
  42  func (t *Ticker) Stop() {
  43  	t.stopOnce.Do(func() { close(t.stop) })
  44  }
  45  
  46  func (t *Ticker) run() {
  47  	c := t.c
  48  	defer close(c)
  49  
  50  	// Ticker is guaranteed to tick at least once.
  51  	afterC := t.send(time.Now())
  52  
  53  	for {
  54  		if afterC == nil {
  55  			return
  56  		}
  57  
  58  		select {
  59  		case tick := <-afterC:
  60  			afterC = t.send(tick)
  61  		case <-t.stop:
  62  			t.c = nil // Prevent future ticks from being sent to the channel.
  63  			return
  64  		}
  65  	}
  66  }
  67  
  68  func (t *Ticker) send(tick time.Time) <-chan time.Time {
  69  	select {
  70  	case t.c <- tick:
  71  	case <-t.stop:
  72  		return nil
  73  	}
  74  
  75  	next := t.b.NextBackOff()
  76  	if next == Stop {
  77  		t.Stop()
  78  		return nil
  79  	}
  80  
  81  	t.timer.Start(next)
  82  	return t.timer.C()
  83  }
  84