github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/synckit/backoff.go (about)

     1  // Copyright 2020 Insolar Network Ltd.
     2  // All rights reserved.
     3  // This material is licensed under the Insolar License version 1.0,
     4  // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md.
     5  
     6  // Package backoff provides an exponential-backoff implementation.
     7  package synckit
     8  
     9  import (
    10  	"math"
    11  	"math/rand"
    12  	"time"
    13  )
    14  
    15  // Backoff is a time.Duration counter, starting at Min. After every call to
    16  // the Duration method the current timing is multiplied by Factor, but it
    17  // never exceeds Max.
    18  //
    19  // Backoff is not generally concurrent-safe, but the ForAttempt method can
    20  // be used concurrently.
    21  type Backoff struct {
    22  	attempt int
    23  	// Factor is the multiplying factor for each increment step
    24  	Factor float64
    25  	// Jitter eases contention by randomizing backoff steps
    26  	Jitter bool
    27  	// Min and Max are the minimum and maximum values of the counter
    28  	Min, Max time.Duration
    29  }
    30  
    31  // Duration returns the duration for the current attempt before incrementing
    32  // the attempt counter. See ForAttempt.
    33  func (b *Backoff) Duration() time.Duration {
    34  	d := b.ForAttempt(b.attempt)
    35  	b.attempt++
    36  	return d
    37  }
    38  
    39  const maxInt64 = float64(math.MaxInt64 - 512)
    40  
    41  // ForAttempt returns the duration for a specific attempt. This is useful if
    42  // you have a large number of independent Backoffs, but don't want use
    43  // unnecessary memory storing the Backoff parameters per Backoff. The first
    44  // attempt should be 0.
    45  //
    46  // ForAttempt is concurrent-safe.
    47  func (b *Backoff) ForAttempt(attempt int) time.Duration {
    48  	// Zero-values are nonsensical, so we use
    49  	// them to apply defaults
    50  	min := b.Min
    51  	if min <= 0 {
    52  		min = 100 * time.Millisecond
    53  	}
    54  	max := b.Max
    55  	if max <= 0 {
    56  		max = 10 * time.Second
    57  	}
    58  	if min >= max {
    59  		// short-circuit
    60  		return max
    61  	}
    62  	factor := b.Factor
    63  	if factor <= 0 {
    64  		factor = 2
    65  	}
    66  	// calculate this duration
    67  	minf := float64(min)
    68  	durf := minf * math.Pow(factor, float64(attempt))
    69  	if b.Jitter {
    70  		durf = rand.Float64()*(durf-minf) + minf
    71  	}
    72  	// ensure float64 wont overflow int64
    73  	if durf > maxInt64 {
    74  		return max
    75  	}
    76  	dur := time.Duration(durf)
    77  	// keep within bounds
    78  	if dur < min {
    79  		return min
    80  	}
    81  	if dur > max {
    82  		return max
    83  	}
    84  	return dur
    85  }
    86  
    87  // Reset restarts the current attempt counter at zero.
    88  func (b *Backoff) Reset() {
    89  	b.attempt = 0
    90  }
    91  
    92  // Attempt returns the current attempt counter value.
    93  func (b *Backoff) Attempt() int {
    94  	return b.attempt
    95  }
    96  
    97  // Copy returns a backoff with equals constraints as the original
    98  func (b *Backoff) Copy() *Backoff {
    99  	return &Backoff{
   100  		Factor: b.Factor,
   101  		Jitter: b.Jitter,
   102  		Min:    b.Min,
   103  		Max:    b.Max,
   104  	}
   105  }