Class RetryBackoff

java.lang.Object
org.microproxy.RetryBackoff

public final class RetryBackoff extends Object
How long the proxy waits before trying the next route after a connection attempt failed: the next chained proxy, or the direct fallback, that the ChainedProxyManager offered. Set for the whole server with HttpProxyServerBootstrap.withChainedProxyRetryBackoff(RetryBackoff) and for one request with HttpFilters.chainedProxyRetryBackoff().

The built-in policies:

  • none(): no waiting; the next route is tried at once (the default).
  • fixed(Duration): the same wait before every retry.
  • exponential(Duration, Duration): deterministic doubling, initial, 2 x initial, 4 x initial, ... capped at max, without jitter. Each wait is known in advance, which suits tests that depend on exact timings, as LittleProxy forks that added a backoff did.
  • exponentialWithJitter(Duration, Duration): the same doubling ceiling, but each wait is a uniformly random time between zero and that ceiling ("full jitter"), so that clients failing together do not retry together.

Attempts are counted from the first: the wait before attempt n + 1 is delay(n), where n attempts have failed.

The waits of one request add up to at most a budget; once it is spent, the remaining routes are tried without waiting. By default the budget is the request's connect timeout, at most 30 seconds, so a long route list cannot hold a request for minutes. withBudget(Duration) sets another total, and unbudgeted() lifts it: every wait the policy names is then waited, as LittleProxy forks that added a backoff did. Either way a client that leaves ends the wait.

Instances are immutable and thread-safe, and equal when they wait the same way.

  • Method Details

    • none

      public static RetryBackoff none()
      No waiting: the next route is tried as soon as an attempt fails.
      Returns:
      the policy that never waits
    • fixed

      public static RetryBackoff fixed(Duration delay)
      The same wait before every retry, without jitter: exponential(delay, delay).
      Parameters:
      delay - the wait, positive
      Returns:
      the policy
      Throws:
      IllegalArgumentException - if delay is not positive
      NullPointerException - if delay is null
    • exponential

      public static RetryBackoff exponential(Duration initial, Duration max)
      Deterministic doubling: waits initial before the second attempt, then twice as long before each further one, capped at max, without jitter. For example exponential(100 ms, 1 s) waits 100, 200, 400, 800, 1000, 1000, ... ms.
      Parameters:
      initial - the wait before the second attempt, positive
      max - the longest wait, at least initial
      Returns:
      the policy
      Throws:
      IllegalArgumentException - if initial is not positive or max is below it
      NullPointerException - if either is null
    • exponentialWithJitter

      public static RetryBackoff exponentialWithJitter(Duration initial, Duration max)
      Exponential backoff with "full jitter": before attempt n + 1, a uniformly random time from zero up to (not including) the exponential(java.time.Duration, java.time.Duration) wait min(max, initial x 2^(n-1)). On average it waits half as long as exponential(java.time.Duration, java.time.Duration), and spreads the retries of clients that failed at the same time.
      Parameters:
      initial - the ceiling of the wait before the second attempt, positive
      max - the ceiling of every wait, at least initial
      Returns:
      the policy
      Throws:
      IllegalArgumentException - if initial is not positive or max is below it
      NullPointerException - if either is null
    • withBudget

      public RetryBackoff withBudget(Duration total)
      This policy with total as the most that all waits of one request may add up to, in place of the default (the request's connect timeout, at most 30 seconds). Once the budget is spent, the remaining routes are tried without waiting; the wait that would overrun it is shortened to what is left. A budget too long to count in nanoseconds (about 292 years) is no budget, as unbudgeted(). none() never waits, and stays as it is.
      Parameters:
      total - the budget for all waits of one request, positive
      Returns:
      the policy with that budget
      Throws:
      IllegalArgumentException - if total is not positive
      NullPointerException - if total is null
    • unbudgeted

      public RetryBackoff unbudgeted()
      This policy without a budget: every wait it names is waited, however long they add up to (the route list and each wait's max() still bound them), as LittleProxy forks that added a backoff did. A client that leaves still ends the wait. none() never waits, and stays as it is.
      Returns:
      the policy without a budget
    • withDefaultBudget

      public RetryBackoff withDefaultBudget()
      This policy with the default budget again: the request's connect timeout, at most 30 seconds.
      Returns:
      the policy with the default budget
    • budget

      public Optional<Duration> budget()
      The budget set with withBudget(Duration): the most all waits of one request may add up to. Empty when the default budget applies (the request's connect timeout, at most 30 seconds) and when there is none (isUnbudgeted()).
      Returns:
      the policy's own budget, or empty
    • isUnbudgeted

      public boolean isUnbudgeted()
      Whether the waits of one request have no budget (unbudgeted()).
      Returns:
      whether every wait the policy names is waited
    • isNone

      public boolean isNone()
      Whether this policy never waits (none()).
      Returns:
      whether every delay is zero
    • initial

      public Duration initial()
      The wait before the second attempt (its ceiling, with jitter); zero for none().
      Returns:
      the initial wait
    • max

      public Duration max()
      The longest wait; zero for none().
      Returns:
      the maximum wait
    • jittered

      public boolean jittered()
      Whether the waits are random (exponentialWithJitter(java.time.Duration, java.time.Duration)) rather than exact.
      Returns:
      whether the policy jitters its waits
    • ceiling

      public Duration ceiling(int failedAttempts)
      The longest wait before the attempt that follows failedAttempts failed ones: min(max, initial x 2^(failedAttempts-1)). Without jitter it is the wait itself.
      Parameters:
      failedAttempts - how many attempts have failed, at least 1
      Returns:
      the wait's ceiling
      Throws:
      IllegalArgumentException - if failedAttempts is below 1
    • delay

      public Duration delay(int failedAttempts)
      The wait before the attempt that follows failedAttempts failed ones, drawing the jitter (if any) from a ThreadLocalRandom.
      Parameters:
      failedAttempts - how many attempts have failed, at least 1
      Returns:
      the wait
      Throws:
      IllegalArgumentException - if failedAttempts is below 1
    • delay

      public Duration delay(int failedAttempts, RandomGenerator random)
      The wait before the attempt that follows failedAttempts failed ones. With jitter it is the ceiling(int) scaled by random.nextDouble() (clamped to [0, 1]). Without jitter it is the ceiling, and random is not used.
      Parameters:
      failedAttempts - how many attempts have failed, at least 1
      random - where the jitter comes from
      Returns:
      the wait
      Throws:
      IllegalArgumentException - if failedAttempts is below 1
    • equals

      public boolean equals(Object o)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object
    • toString

      public String toString()
      Returns how the policy is made, such as exponential(PT0.1S, PT2S) or fixed(PT1S).unbudgeted().
      Overrides:
      toString in class Object
      Returns:
      how the policy is made, such as exponential(PT0.1S, PT2S) or fixed(PT1S).unbudgeted()