mirror of
				https://source.quilibrium.com/quilibrium/ceremonyclient.git
				synced 2025-11-04 13:27:27 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			233 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package backoff
 | 
						|
 | 
						|
import (
 | 
						|
	"math"
 | 
						|
	"math/rand"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	logging "github.com/ipfs/go-log/v2"
 | 
						|
)
 | 
						|
 | 
						|
var log = logging.Logger("discovery-backoff")
 | 
						|
 | 
						|
type BackoffFactory func() BackoffStrategy
 | 
						|
 | 
						|
// BackoffStrategy describes how backoff will be implemented. BackoffStrategies are stateful.
 | 
						|
type BackoffStrategy interface {
 | 
						|
	// Delay calculates how long the next backoff duration should be, given the prior calls to Delay
 | 
						|
	Delay() time.Duration
 | 
						|
	// Reset clears the internal state of the BackoffStrategy
 | 
						|
	Reset()
 | 
						|
}
 | 
						|
 | 
						|
// Jitter implementations taken roughly from https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
 | 
						|
 | 
						|
// Jitter must return a duration between min and max. Min must be lower than, or equal to, max.
 | 
						|
type Jitter func(duration, min, max time.Duration, rng *rand.Rand) time.Duration
 | 
						|
 | 
						|
// FullJitter returns a random number, uniformly chosen from the range [min, boundedDur].
 | 
						|
// boundedDur is the duration bounded between min and max.
 | 
						|
func FullJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration {
 | 
						|
	if duration <= min {
 | 
						|
		return min
 | 
						|
	}
 | 
						|
 | 
						|
	normalizedDur := boundedDuration(duration, min, max) - min
 | 
						|
 | 
						|
	return boundedDuration(time.Duration(rng.Int63n(int64(normalizedDur)))+min, min, max)
 | 
						|
}
 | 
						|
 | 
						|
// NoJitter returns the duration bounded between min and max
 | 
						|
func NoJitter(duration, min, max time.Duration, rng *rand.Rand) time.Duration {
 | 
						|
	return boundedDuration(duration, min, max)
 | 
						|
}
 | 
						|
 | 
						|
type randomizedBackoff struct {
 | 
						|
	min time.Duration
 | 
						|
	max time.Duration
 | 
						|
	rng *rand.Rand
 | 
						|
}
 | 
						|
 | 
						|
func (b *randomizedBackoff) BoundedDelay(duration time.Duration) time.Duration {
 | 
						|
	return boundedDuration(duration, b.min, b.max)
 | 
						|
}
 | 
						|
 | 
						|
func boundedDuration(d, min, max time.Duration) time.Duration {
 | 
						|
	if d < min {
 | 
						|
		return min
 | 
						|
	}
 | 
						|
	if d > max {
 | 
						|
		return max
 | 
						|
	}
 | 
						|
	return d
 | 
						|
}
 | 
						|
 | 
						|
type attemptBackoff struct {
 | 
						|
	attempt int
 | 
						|
	jitter  Jitter
 | 
						|
	randomizedBackoff
 | 
						|
}
 | 
						|
 | 
						|
func (b *attemptBackoff) Reset() {
 | 
						|
	b.attempt = 0
 | 
						|
}
 | 
						|
 | 
						|
// NewFixedBackoff creates a BackoffFactory with a constant backoff duration
 | 
						|
func NewFixedBackoff(delay time.Duration) BackoffFactory {
 | 
						|
	return func() BackoffStrategy {
 | 
						|
		return &fixedBackoff{delay: delay}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type fixedBackoff struct {
 | 
						|
	delay time.Duration
 | 
						|
}
 | 
						|
 | 
						|
func (b *fixedBackoff) Delay() time.Duration {
 | 
						|
	return b.delay
 | 
						|
}
 | 
						|
 | 
						|
func (b *fixedBackoff) Reset() {}
 | 
						|
 | 
						|
// NewPolynomialBackoff creates a BackoffFactory with backoff of the form c0*x^0, c1*x^1, ...cn*x^n where x is the attempt number
 | 
						|
// jitter is the function for adding randomness around the backoff
 | 
						|
// timeUnits are the units of time the polynomial is evaluated in
 | 
						|
// polyCoefs is the array of polynomial coefficients from [c0, c1, ... cn]
 | 
						|
func NewPolynomialBackoff(min, max time.Duration, jitter Jitter,
 | 
						|
	timeUnits time.Duration, polyCoefs []float64, rngSrc rand.Source) BackoffFactory {
 | 
						|
	rng := rand.New(&lockedSource{src: rngSrc})
 | 
						|
	return func() BackoffStrategy {
 | 
						|
		return &polynomialBackoff{
 | 
						|
			attemptBackoff: attemptBackoff{
 | 
						|
				randomizedBackoff: randomizedBackoff{
 | 
						|
					min: min,
 | 
						|
					max: max,
 | 
						|
					rng: rng,
 | 
						|
				},
 | 
						|
				jitter: jitter,
 | 
						|
			},
 | 
						|
			timeUnits: timeUnits,
 | 
						|
			poly:      polyCoefs,
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type polynomialBackoff struct {
 | 
						|
	attemptBackoff
 | 
						|
	timeUnits time.Duration
 | 
						|
	poly      []float64
 | 
						|
}
 | 
						|
 | 
						|
func (b *polynomialBackoff) Delay() time.Duration {
 | 
						|
	var polySum float64
 | 
						|
	switch len(b.poly) {
 | 
						|
	case 0:
 | 
						|
		return 0
 | 
						|
	case 1:
 | 
						|
		polySum = b.poly[0]
 | 
						|
	default:
 | 
						|
		polySum = b.poly[0]
 | 
						|
		exp := 1
 | 
						|
		attempt := b.attempt
 | 
						|
		b.attempt++
 | 
						|
 | 
						|
		for _, c := range b.poly[1:] {
 | 
						|
			exp *= attempt
 | 
						|
			polySum += float64(exp) * c
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return b.jitter(time.Duration(float64(b.timeUnits)*polySum), b.min, b.max, b.rng)
 | 
						|
}
 | 
						|
 | 
						|
// NewExponentialBackoff creates a BackoffFactory with backoff of the form base^x + offset where x is the attempt number
 | 
						|
// jitter is the function for adding randomness around the backoff
 | 
						|
// timeUnits are the units of time the base^x is evaluated in
 | 
						|
func NewExponentialBackoff(min, max time.Duration, jitter Jitter,
 | 
						|
	timeUnits time.Duration, base float64, offset time.Duration, rngSrc rand.Source) BackoffFactory {
 | 
						|
	rng := rand.New(&lockedSource{src: rngSrc})
 | 
						|
	return func() BackoffStrategy {
 | 
						|
		return &exponentialBackoff{
 | 
						|
			attemptBackoff: attemptBackoff{
 | 
						|
				randomizedBackoff: randomizedBackoff{
 | 
						|
					min: min,
 | 
						|
					max: max,
 | 
						|
					rng: rng,
 | 
						|
				},
 | 
						|
				jitter: jitter,
 | 
						|
			},
 | 
						|
			timeUnits: timeUnits,
 | 
						|
			base:      base,
 | 
						|
			offset:    offset,
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type exponentialBackoff struct {
 | 
						|
	attemptBackoff
 | 
						|
	timeUnits time.Duration
 | 
						|
	base      float64
 | 
						|
	offset    time.Duration
 | 
						|
}
 | 
						|
 | 
						|
func (b *exponentialBackoff) Delay() time.Duration {
 | 
						|
	attempt := b.attempt
 | 
						|
	b.attempt++
 | 
						|
	return b.jitter(
 | 
						|
		time.Duration(math.Pow(b.base, float64(attempt))*float64(b.timeUnits))+b.offset, b.min, b.max, b.rng)
 | 
						|
}
 | 
						|
 | 
						|
// NewExponentialDecorrelatedJitter creates a BackoffFactory with backoff of the roughly of the form base^x where x is the attempt number.
 | 
						|
// Delays start at the minimum duration and after each attempt delay = rand(min, delay * base), bounded by the max
 | 
						|
// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for more information
 | 
						|
func NewExponentialDecorrelatedJitter(min, max time.Duration, base float64, rngSrc rand.Source) BackoffFactory {
 | 
						|
	rng := rand.New(&lockedSource{src: rngSrc})
 | 
						|
	return func() BackoffStrategy {
 | 
						|
		return &exponentialDecorrelatedJitter{
 | 
						|
			randomizedBackoff: randomizedBackoff{
 | 
						|
				min: min,
 | 
						|
				max: max,
 | 
						|
				rng: rng,
 | 
						|
			},
 | 
						|
			base: base,
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type exponentialDecorrelatedJitter struct {
 | 
						|
	randomizedBackoff
 | 
						|
	base      float64
 | 
						|
	lastDelay time.Duration
 | 
						|
}
 | 
						|
 | 
						|
func (b *exponentialDecorrelatedJitter) Delay() time.Duration {
 | 
						|
	if b.lastDelay < b.min {
 | 
						|
		b.lastDelay = b.min
 | 
						|
		return b.lastDelay
 | 
						|
	}
 | 
						|
 | 
						|
	nextMax := int64(float64(b.lastDelay) * b.base)
 | 
						|
	b.lastDelay = boundedDuration(time.Duration(b.rng.Int63n(nextMax-int64(b.min)))+b.min, b.min, b.max)
 | 
						|
	return b.lastDelay
 | 
						|
}
 | 
						|
 | 
						|
func (b *exponentialDecorrelatedJitter) Reset() { b.lastDelay = 0 }
 | 
						|
 | 
						|
type lockedSource struct {
 | 
						|
	lk  sync.Mutex
 | 
						|
	src rand.Source
 | 
						|
}
 | 
						|
 | 
						|
func (r *lockedSource) Int63() (n int64) {
 | 
						|
	r.lk.Lock()
 | 
						|
	n = r.src.Int63()
 | 
						|
	r.lk.Unlock()
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (r *lockedSource) Seed(seed int64) {
 | 
						|
	r.lk.Lock()
 | 
						|
	r.src.Seed(seed)
 | 
						|
	r.lk.Unlock()
 | 
						|
}
 |