All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.api.client.util.ExponentialBackOff Maven / Gradle / Ivy

Go to download

Google HTTP Client Library for Java. Functionality that works on all supported Java platforms, including Java 5 (or higher) desktop (SE) and web (EE), Android, and Google App Engine.

There is a newer version: 1.44.1
Show newest version
/*
 * Copyright (c) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.client.util;

import java.io.IOException;

/**
 * Implementation of {@link BackOff} that increases the back off period for each retry attempt using
 * a randomization function that grows exponentially.
 *
 * 

* {@link #nextBackOffMillis()} is calculated using the following formula: *

* *
   randomized_interval =
       retry_interval * (random value in range [1 - randomization_factor, 1 + randomization_factor])
 * 
* *

* In other words {@link #nextBackOffMillis()} will range between the randomization factor * percentage below and above the retry interval. For example, using 2 seconds as the base retry * interval and 0.5 as the randomization factor, the actual back off period used in the next retry * attempt will be between 1 and 3 seconds. *

* *

* Note: max_interval caps the retry_interval and not the randomized_interval. *

* *

* If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning * {@link BackOff#STOP}. The elapsed time can be reset by calling {@link #reset()}. *

* *

* Example: The default retry_interval is .5 seconds, default randomization_factor is 0.5, default * multiplier is 1.5 and the default max_interval is 1 minute. For 10 tries the sequence will be * (values in seconds) and assuming we go over the max_elapsed_time on the 10th try: *

* *
   request#     retry_interval     randomized_interval

   1             0.5                [0.25,   0.75]
   2             0.75               [0.375,  1.125]
   3             1.125              [0.562,  1.687]
   4             1.687              [0.8435, 2.53]
   5             2.53               [1.265,  3.795]
   6             3.795              [1.897,  5.692]
   7             5.692              [2.846,  8.538]
   8             8.538              [4.269, 12.807]
   9            12.807              [6.403, 19.210]
   10           19.210              {@link BackOff#STOP}
 * 
* *

* Implementation is not thread-safe. *

* * @since 1.15 * @author Ravi Mistry */ public class ExponentialBackOff implements BackOff { /** The default initial interval value in milliseconds (0.5 seconds). */ public static final int DEFAULT_INITIAL_INTERVAL_MILLIS = 500; /** * The default randomization factor (0.5 which results in a random period ranging between 50% * below and 50% above the retry interval). */ public static final double DEFAULT_RANDOMIZATION_FACTOR = 0.5; /** The default multiplier value (1.5 which is 50% increase per back off). */ public static final double DEFAULT_MULTIPLIER = 1.5; /** The default maximum back off time in milliseconds (1 minute). */ public static final int DEFAULT_MAX_INTERVAL_MILLIS = 60000; /** The default maximum elapsed time in milliseconds (15 minutes). */ public static final int DEFAULT_MAX_ELAPSED_TIME_MILLIS = 900000; /** The current retry interval in milliseconds. */ private int currentIntervalMillis; /** The initial retry interval in milliseconds. */ private final int initialIntervalMillis; /** * The randomization factor to use for creating a range around the retry interval. * *

* A randomization factor of 0.5 results in a random period ranging between 50% below and 50% * above the retry interval. *

*/ private final double randomizationFactor; /** The value to multiply the current interval with for each retry attempt. */ private final double multiplier; /** * The maximum value of the back off period in milliseconds. Once the retry interval reaches this * value it stops increasing. */ private final int maxIntervalMillis; /** * The system time in nanoseconds. It is calculated when an ExponentialBackOffPolicy instance is * created and is reset when {@link #reset()} is called. */ long startTimeNanos; /** * The maximum elapsed time after instantiating {@link ExponentialBackOff} or calling * {@link #reset()} after which {@link #nextBackOffMillis()} returns {@link BackOff#STOP}. */ private final int maxElapsedTimeMillis; /** Nano clock. */ private final NanoClock nanoClock; /** * Creates an instance of ExponentialBackOffPolicy using default values. * *

* To override the defaults use {@link Builder}. *

* *
    *
  • {@code initialIntervalMillis} defaults to {@link #DEFAULT_INITIAL_INTERVAL_MILLIS}
  • *
  • {@code randomizationFactor} defaults to {@link #DEFAULT_RANDOMIZATION_FACTOR}
  • *
  • {@code multiplier} defaults to {@link #DEFAULT_MULTIPLIER}
  • *
  • {@code maxIntervalMillis} defaults to {@link #DEFAULT_MAX_INTERVAL_MILLIS}
  • *
  • {@code maxElapsedTimeMillis} defaults in {@link #DEFAULT_MAX_ELAPSED_TIME_MILLIS}
  • *
*/ public ExponentialBackOff() { this(new Builder()); } /** * @param builder builder */ protected ExponentialBackOff(Builder builder) { initialIntervalMillis = builder.initialIntervalMillis; randomizationFactor = builder.randomizationFactor; multiplier = builder.multiplier; maxIntervalMillis = builder.maxIntervalMillis; maxElapsedTimeMillis = builder.maxElapsedTimeMillis; nanoClock = builder.nanoClock; Preconditions.checkArgument(initialIntervalMillis > 0); Preconditions.checkArgument(0 <= randomizationFactor && randomizationFactor < 1); Preconditions.checkArgument(multiplier >= 1); Preconditions.checkArgument(maxIntervalMillis >= initialIntervalMillis); Preconditions.checkArgument(maxElapsedTimeMillis > 0); reset(); } /** Sets the interval back to the initial retry interval and restarts the timer. */ public final void reset() { currentIntervalMillis = initialIntervalMillis; startTimeNanos = nanoClock.nanoTime(); } /** * {@inheritDoc} * *

* This method calculates the next back off interval using the formula: randomized_interval = * retry_interval +/- (randomization_factor * retry_interval) *

* *

* Subclasses may override if a different algorithm is required. *

*/ public long nextBackOffMillis() throws IOException { // Make sure we have not gone over the maximum elapsed time. if (getElapsedTimeMillis() > maxElapsedTimeMillis) { return STOP; } int randomizedInterval = getRandomValueFromInterval(randomizationFactor, Math.random(), currentIntervalMillis); incrementCurrentInterval(); return randomizedInterval; } /** * Returns a random value from the interval [randomizationFactor * currentInterval, * randomizationFactor * currentInterval]. */ static int getRandomValueFromInterval( double randomizationFactor, double random, int currentIntervalMillis) { double delta = randomizationFactor * currentIntervalMillis; double minInterval = currentIntervalMillis - delta; double maxInterval = currentIntervalMillis + delta; // Get a random value from the range [minInterval, maxInterval]. // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then // we want a 33% chance for selecting either 1, 2 or 3. int randomValue = (int) (minInterval + (random * (maxInterval - minInterval + 1))); return randomValue; } /** Returns the initial retry interval in milliseconds. */ public final int getInitialIntervalMillis() { return initialIntervalMillis; } /** * Returns the randomization factor to use for creating a range around the retry interval. * *

* A randomization factor of 0.5 results in a random period ranging between 50% below and 50% * above the retry interval. *

*/ public final double getRandomizationFactor() { return randomizationFactor; } /** * Returns the current retry interval in milliseconds. */ public final int getCurrentIntervalMillis() { return currentIntervalMillis; } /** * Returns the value to multiply the current interval with for each retry attempt. */ public final double getMultiplier() { return multiplier; } /** * Returns the maximum value of the back off period in milliseconds. Once the current interval * reaches this value it stops increasing. */ public final int getMaxIntervalMillis() { return maxIntervalMillis; } /** * Returns the maximum elapsed time in milliseconds. * *

* If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning * {@link BackOff#STOP}. The elapsed time can be reset by calling {@link #reset()}. *

*/ public final int getMaxElapsedTimeMillis() { return maxElapsedTimeMillis; } /** * Returns the elapsed time in milliseconds since an {@link ExponentialBackOff} instance is * created and is reset when {@link #reset()} is called. * *

* The elapsed time is computed using {@link System#nanoTime()}. *

*/ public final long getElapsedTimeMillis() { return (nanoClock.nanoTime() - startTimeNanos) / 1000000; } /** * Increments the current interval by multiplying it with the multiplier. */ private void incrementCurrentInterval() { // Check for overflow, if overflow is detected set the current interval to the max interval. if (currentIntervalMillis >= maxIntervalMillis / multiplier) { currentIntervalMillis = maxIntervalMillis; } else { currentIntervalMillis *= multiplier; } } /** * Builder for {@link ExponentialBackOff}. * *

* Implementation is not thread-safe. *

*/ public static class Builder { /** The initial retry interval in milliseconds. */ int initialIntervalMillis = DEFAULT_INITIAL_INTERVAL_MILLIS; /** * The randomization factor to use for creating a range around the retry interval. * *

* A randomization factor of 0.5 results in a random period ranging between 50% below and 50% * above the retry interval. *

*/ double randomizationFactor = DEFAULT_RANDOMIZATION_FACTOR; /** The value to multiply the current interval with for each retry attempt. */ double multiplier = DEFAULT_MULTIPLIER; /** * The maximum value of the back off period in milliseconds. Once the retry interval reaches * this value it stops increasing. */ int maxIntervalMillis = DEFAULT_MAX_INTERVAL_MILLIS; /** * The maximum elapsed time in milliseconds after instantiating {@link ExponentialBackOff} or * calling {@link #reset()} after which {@link #nextBackOffMillis()} returns * {@link BackOff#STOP}. */ int maxElapsedTimeMillis = DEFAULT_MAX_ELAPSED_TIME_MILLIS; /** Nano clock. */ NanoClock nanoClock = NanoClock.SYSTEM; public Builder() { } /** Builds a new instance of {@link ExponentialBackOff}. */ public ExponentialBackOff build() { return new ExponentialBackOff(this); } /** * Returns the initial retry interval in milliseconds. The default value is * {@link #DEFAULT_INITIAL_INTERVAL_MILLIS}. */ public final int getInitialIntervalMillis() { return initialIntervalMillis; } /** * Sets the initial retry interval in milliseconds. The default value is * {@link #DEFAULT_INITIAL_INTERVAL_MILLIS}. Must be {@code > 0}. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setInitialIntervalMillis(int initialIntervalMillis) { this.initialIntervalMillis = initialIntervalMillis; return this; } /** * Returns the randomization factor to use for creating a range around the retry interval. The * default value is {@link #DEFAULT_RANDOMIZATION_FACTOR}. * *

* A randomization factor of 0.5 results in a random period ranging between 50% below and 50% * above the retry interval. *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public final double getRandomizationFactor() { return randomizationFactor; } /** * Sets the randomization factor to use for creating a range around the retry interval. The * default value is {@link #DEFAULT_RANDOMIZATION_FACTOR}. Must fall in the range * {@code 0 <= randomizationFactor < 1}. * *

* A randomization factor of 0.5 results in a random period ranging between 50% below and 50% * above the retry interval. *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setRandomizationFactor(double randomizationFactor) { this.randomizationFactor = randomizationFactor; return this; } /** * Returns the value to multiply the current interval with for each retry attempt. The default * value is {@link #DEFAULT_MULTIPLIER}. */ public final double getMultiplier() { return multiplier; } /** * Sets the value to multiply the current interval with for each retry attempt. The default * value is {@link #DEFAULT_MULTIPLIER}. Must be {@code >= 1}. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setMultiplier(double multiplier) { this.multiplier = multiplier; return this; } /** * Returns the maximum value of the back off period in milliseconds. Once the current interval * reaches this value it stops increasing. The default value is * {@link #DEFAULT_MAX_INTERVAL_MILLIS}. Must be {@code >= initialInterval}. */ public final int getMaxIntervalMillis() { return maxIntervalMillis; } /** * Sets the maximum value of the back off period in milliseconds. Once the current interval * reaches this value it stops increasing. The default value is * {@link #DEFAULT_MAX_INTERVAL_MILLIS}. * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setMaxIntervalMillis(int maxIntervalMillis) { this.maxIntervalMillis = maxIntervalMillis; return this; } /** * Returns the maximum elapsed time in milliseconds. The default value is * {@link #DEFAULT_MAX_ELAPSED_TIME_MILLIS}. * *

* If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning * {@link BackOff#STOP}. The elapsed time can be reset by calling {@link #reset()}. *

*/ public final int getMaxElapsedTimeMillis() { return maxElapsedTimeMillis; } /** * Sets the maximum elapsed time in milliseconds. The default value is * {@link #DEFAULT_MAX_ELAPSED_TIME_MILLIS}. Must be {@code > 0}. * *

* If the time elapsed since an {@link ExponentialBackOff} instance is created goes past the * max_elapsed_time then the method {@link #nextBackOffMillis()} starts returning * {@link BackOff#STOP}. The elapsed time can be reset by calling {@link #reset()}. *

* *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setMaxElapsedTimeMillis(int maxElapsedTimeMillis) { this.maxElapsedTimeMillis = maxElapsedTimeMillis; return this; } /** Returns the nano clock. */ public final NanoClock getNanoClock() { return nanoClock; } /** * Sets the nano clock ({@link NanoClock#SYSTEM} by default). * *

* Overriding is only supported for the purpose of calling the super implementation and changing * the return type, but nothing else. *

*/ public Builder setNanoClock(NanoClock nanoClock) { this.nanoClock = Preconditions.checkNotNull(nanoClock); return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy