github.com/blend/go-sdk@v1.20220411.3/grpcutil/backoff.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package grpcutil
     9  
    10  import (
    11  	"context"
    12  	"math/rand"
    13  	"time"
    14  )
    15  
    16  // BackoffFunc denotes a family of functions that control the backoff duration between call retries.
    17  //
    18  // They are called with an identifier of the attempt, and should return a time the system client should
    19  // hold off for. If the time returned is longer than the `context.Context.Deadline` of the request
    20  // the deadline of the request takes precedence and the wait will be interrupted before proceeding
    21  // with the next iteration.
    22  type BackoffFunc func(attempt uint) time.Duration
    23  
    24  // BackoffFuncContext denotes a family of functions that control the backoff duration between call retries.
    25  //
    26  // They are called with an identifier of the attempt, and should return a time the system client should
    27  // hold off for. If the time returned is longer than the `context.Context.Deadline` of the request
    28  // the deadline of the request takes precedence and the wait will be interrupted before proceeding
    29  // with the next iteration. The context can be used to extract request scoped metadata and context values.
    30  type BackoffFuncContext func(ctx context.Context, attempt uint) time.Duration
    31  
    32  // BackoffLinear is very simple: it waits for a fixed period of time between calls.
    33  func BackoffLinear(waitBetween time.Duration) BackoffFunc {
    34  	return func(attempt uint) time.Duration {
    35  		return waitBetween
    36  	}
    37  }
    38  
    39  // BackoffLinearWithJitter waits a set period of time, allowing for jitter (fractional adjustment).
    40  //
    41  // For example waitBetween=1s and jitter=0.10 can generate waits between 900ms and 1100ms.
    42  func BackoffLinearWithJitter(waitBetween time.Duration, jitterFraction float64) BackoffFunc {
    43  	return func(attempt uint) time.Duration {
    44  		return JitterUp(waitBetween, jitterFraction)
    45  	}
    46  }
    47  
    48  // BackoffExponential produces increasing intervals for each attempt.
    49  //
    50  // The scalar is multiplied times 2 raised to the current attempt. So the first
    51  // retry with a scalar of 100ms is 100ms, while the 5th attempt would be 1.6s.
    52  func BackoffExponential(scalar time.Duration) BackoffFunc {
    53  	return func(attempt uint) time.Duration {
    54  		return scalar * time.Duration(ExponentBase2(attempt))
    55  	}
    56  }
    57  
    58  // BackoffExponentialWithJitter creates an exponential backoff like
    59  // BackoffExponential does, but adds jitter.
    60  func BackoffExponentialWithJitter(scalar time.Duration, jitterFraction float64) BackoffFunc {
    61  	return func(attempt uint) time.Duration {
    62  		return JitterUp(scalar*time.Duration(ExponentBase2(attempt)), jitterFraction)
    63  	}
    64  }
    65  
    66  // JitterUp adds random jitter to the duration.
    67  //
    68  // This adds or subtracts time from the duration within a given jitter fraction.
    69  // For example for 10s and jitter 0.1, it will return a time within [9s, 11s])
    70  func JitterUp(duration time.Duration, jitter float64) time.Duration {
    71  	multiplier := jitter * (rand.Float64()*2 - 1)
    72  	return time.Duration(float64(duration) * (1 + multiplier))
    73  }
    74  
    75  // ExponentBase2 computes 2^(a-1) where a >= 1. If a is 0, the result is 0.
    76  func ExponentBase2(a uint) uint {
    77  	return (1 << a) >> 1
    78  }