org.infinispan.util.ExponentialBackOffImpl Maven / Gradle / Ivy
package org.infinispan.util;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.infinispan.commons.util.Util;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* The default {@link ExponentialBackOff} implementation for IRAC (asynchronous cross-site replication).
*
* An exponential back-off implementation with min interval of 500 ms and a maximum of 300'000 ms (5 min). It uses a
* multiplier of 2 (each timeslot will be increase + 100% for each consecutive retry) and the final wait time is
* randomized, +- 50% of the timeslot.
*
* @author Pedro Ruivo
* @since 12.0
*/
public class ExponentialBackOffImpl implements ExponentialBackOff {
private static final Log log = LogFactory.getLog(ExponentialBackOffImpl.class);
//TODO currently only used by IRAC. If required, make it configurable (those 4 constants) to cover other uses cases.
//multiplier value (2 == +100% per retry)
private static final double MULTIPLIER = 2;
//initial interval value in milliseconds
private static final int INITIAL_INTERVAL_MILLIS = 500;
//maximum back off time in milliseconds (300 seconds == 5 min)
private static final int MAX_INTERVAL_MILLIS = 300_000;
//randomization factor (0.5 == 50% below and 50% above the retry interval).
private static final double RANDOMIZATION_FACTOR = 0.5;
//the current retry timeout. If a retry occurs, it will wait for this time +- RANDOMIZATION_FACTOR (%)
private int currentIntervalMillis;
private final ScheduledExecutorService delayer;
public ExponentialBackOffImpl(ScheduledExecutorService delayer) {
this.delayer = delayer;
this.currentIntervalMillis = INITIAL_INTERVAL_MILLIS;
}
long nextBackOffMillis() {
//public for unit test purposes.
if (currentIntervalMillis >= MAX_INTERVAL_MILLIS) {
if (log.isTraceEnabled()) {
log.tracef("Next backoff time %s ms", MAX_INTERVAL_MILLIS);
}
return MAX_INTERVAL_MILLIS;
}
int randomIntervalMillis = getRandomValueFromInterval();
incrementCurrentInterval();
if (log.isTraceEnabled()) {
log.tracef("Next backoff time %s ms", randomIntervalMillis);
}
return Math.min(randomIntervalMillis, MAX_INTERVAL_MILLIS);
}
public void reset() {
this.currentIntervalMillis = INITIAL_INTERVAL_MILLIS;
}
@Override
public CompletionStage asyncBackOff() {
CompletableFuture cf = new CompletableFuture<>();
long sleepTime = nextBackOffMillis();
if (log.isTraceEnabled()) {
log.tracef("async backing-off for %s.", Util.prettyPrintTime(sleepTime));
}
delayer.schedule(() -> cf.complete(null), sleepTime, TimeUnit.MILLISECONDS);
return cf;
}
private void incrementCurrentInterval() {
// Check for overflow, if overflow is detected set the current interval to the max interval.
if (currentIntervalMillis >= MAX_INTERVAL_MILLIS) {
currentIntervalMillis = MAX_INTERVAL_MILLIS;
} else {
currentIntervalMillis *= MULTIPLIER;
}
}
private int getRandomValueFromInterval() {
double delta = RANDOMIZATION_FACTOR * currentIntervalMillis;
return (int) (delta + (ThreadLocalRandom.current().nextDouble() * currentIntervalMillis));
}
}