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