package com.boardgamegeek.io; public class ExponentialBackOff implements BackOff { public static final int DEFAULT_INITIAL_INTERVAL_MILLIS = 500; public static final double DEFAULT_RANDOMIZATION_FACTOR = 0.5; public static final double DEFAULT_MULTIPLIER = 1.5; public static final int DEFAULT_MAX_INTERVAL_MILLIS = 60000; public static final int DEFAULT_MAX_ELAPSED_TIME_MILLIS = 900000; private final int initialIntervalMillis; private final double randomizationFactor; private final double multiplier; private final int maxIntervalMillis; private final int maxElapsedTimeMillis; private int currentIntervalMillis; private long startTimeNanos; public ExponentialBackOff() { this(new Builder()); } protected ExponentialBackOff(Builder builder) { initialIntervalMillis = builder.initialIntervalMillis; randomizationFactor = builder.randomizationFactor; multiplier = builder.multiplier; maxIntervalMillis = builder.maxIntervalMillis; maxElapsedTimeMillis = builder.maxElapsedTimeMillis; checkArgument(initialIntervalMillis > 0); checkArgument(0 <= randomizationFactor && randomizationFactor < 1); checkArgument(multiplier >= 1); checkArgument(maxIntervalMillis >= initialIntervalMillis); checkArgument(maxElapsedTimeMillis > 0); reset(); } public final void reset() { currentIntervalMillis = initialIntervalMillis; startTimeNanos = System.nanoTime(); } public long nextBackOffMillis() { if (getElapsedTimeMillis() > maxElapsedTimeMillis) { return STOP; } int randomizedInterval = getRandomValueFromInterval(randomizationFactor, Math.random(), currentIntervalMillis); incrementCurrentInterval(); return randomizedInterval; } static int getRandomValueFromInterval(double randomizationFactor, double random, int currentIntervalMillis) { double delta = randomizationFactor * currentIntervalMillis; double minInterval = currentIntervalMillis - delta; double maxInterval = currentIntervalMillis + delta; return (int) (minInterval + (random * (maxInterval - minInterval + 1))); } public final long getElapsedTimeMillis() { return (System.nanoTime() - startTimeNanos) / 1000000; } 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; } } private static void checkArgument(boolean expression) { if (!expression) { throw new IllegalArgumentException(); } } public static class Builder { int initialIntervalMillis = DEFAULT_INITIAL_INTERVAL_MILLIS; double randomizationFactor = DEFAULT_RANDOMIZATION_FACTOR; double multiplier = DEFAULT_MULTIPLIER; int maxIntervalMillis = DEFAULT_MAX_INTERVAL_MILLIS; int maxElapsedTimeMillis = DEFAULT_MAX_ELAPSED_TIME_MILLIS; public Builder() { } public ExponentialBackOff build() { return new ExponentialBackOff(this); } public Builder setInitialIntervalMillis(int initialIntervalMillis) { this.initialIntervalMillis = initialIntervalMillis; return this; } public Builder setRandomizationFactor(double randomizationFactor) { this.randomizationFactor = randomizationFactor; return this; } public Builder setMultiplier(double multiplier) { this.multiplier = multiplier; return this; } public Builder setMaxIntervalMillis(int maxIntervalMillis) { this.maxIntervalMillis = maxIntervalMillis; return this; } public Builder setMaxElapsedTimeMillis(int maxElapsedTimeMillis) { this.maxElapsedTimeMillis = maxElapsedTimeMillis; return this; } } }