blob: df9d68bce527f2952dd8a6b4569fff26b6c214c2 [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001package backoff
2
3import (
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.
13type 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.
29func 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.
35func 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.
54func (t *Ticker) Stop() {
55 t.stopOnce.Do(func() { close(t.stop) })
56}
57
58func (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
82func (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}