github.com/metacubex/mihomo@v1.18.5/component/slowdown/backoff.go (about)

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