github.com/weaveworks/common@v0.0.0-20230728070032-dd9e68f319d5/backoff/backoff.go (about)

     1  package backoff
     2  
     3  import (
     4  	"time"
     5  
     6  	log "github.com/sirupsen/logrus"
     7  )
     8  
     9  type backoff struct {
    10  	f                          func() (bool, error)
    11  	quit, done                 chan struct{}
    12  	msg                        string
    13  	initialBackoff, maxBackoff time.Duration
    14  }
    15  
    16  // Interface does f in a loop, sleeping for initialBackoff between
    17  // each iterations.  If it hits an error, it exponentially backs
    18  // off to maxBackoff.  Backoff will log when it backs off, but
    19  // will stop logging when it reaches maxBackoff.  It will also
    20  // log on first success in the beginning and after errors.
    21  type Interface interface {
    22  	Start()
    23  	Stop()
    24  	SetInitialBackoff(time.Duration)
    25  	SetMaxBackoff(time.Duration)
    26  }
    27  
    28  // New makes a new Interface
    29  func New(f func() (bool, error), msg string) Interface {
    30  	return &backoff{
    31  		f:              f,
    32  		quit:           make(chan struct{}),
    33  		done:           make(chan struct{}),
    34  		msg:            msg,
    35  		initialBackoff: 10 * time.Second,
    36  		maxBackoff:     60 * time.Second,
    37  	}
    38  }
    39  
    40  func (b *backoff) SetInitialBackoff(d time.Duration) {
    41  	b.initialBackoff = d
    42  }
    43  
    44  func (b *backoff) SetMaxBackoff(d time.Duration) {
    45  	b.maxBackoff = d
    46  }
    47  
    48  // Stop the backoff, and waits for it to stop.
    49  func (b *backoff) Stop() {
    50  	close(b.quit)
    51  	<-b.done
    52  }
    53  
    54  // Start the backoff.  Can only be called once.
    55  func (b *backoff) Start() {
    56  	defer close(b.done)
    57  	backoff := b.initialBackoff
    58  	shouldLog := true
    59  
    60  	for {
    61  		done, err := b.f()
    62  		if done {
    63  			return
    64  		}
    65  
    66  		if err != nil {
    67  			backoff *= 2
    68  			shouldLog = true
    69  			if backoff > b.maxBackoff {
    70  				backoff = b.maxBackoff
    71  				shouldLog = false
    72  			}
    73  		} else {
    74  			backoff = b.initialBackoff
    75  		}
    76  
    77  		if shouldLog {
    78  			if err != nil {
    79  				log.Warnf("Error %s, backing off %s: %s",
    80  					b.msg, backoff, err)
    81  			} else {
    82  				log.Infof("Success %s", b.msg)
    83  			}
    84  		}
    85  
    86  		// Re-enable logging if we came from an error (suppressed or not)
    87  		// since we want to log in case a success follows.
    88  		shouldLog = err != nil
    89  
    90  		select {
    91  		case <-time.After(backoff):
    92  		case <-b.quit:
    93  			return
    94  		}
    95  	}
    96  
    97  }