github.com/cenkalti/backoff/v4@v4.2.1/ticker.go (about)

     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  }