Skip to content

Commit

Permalink
feat(util): add exponential backoff utility
Browse files Browse the repository at this point in the history
(cherry picked from commit d53d7bc)
  • Loading branch information
npepinpe authored and github-actions[bot] committed Nov 6, 2023
1 parent c85e07d commit b3d3236
Showing 1 changed file with 95 additions and 0 deletions.
95 changes: 95 additions & 0 deletions util/src/main/java/io/camunda/zeebe/util/ExponentialBackoff.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Zeebe Community License 1.1. You may not use this file
* except in compliance with the Zeebe Community License 1.1.
*/
package io.camunda.zeebe.util;

import java.util.concurrent.ThreadLocalRandom;
import java.util.function.DoubleSupplier;
import java.util.function.LongUnaryOperator;

/**
* An simple implementation of exponential backoff, which multiples the previous delay with an
* increasing multiplier and adding some jitter to avoid multiple clients polling at the same time
* even with back off.
*
* <p>The next delay is calculated as:
*
* <pre> max(min(maxDelay, currentDelay * backoffFactor), minDelay) + (rand(0.0, 1.0) *
* (currentDelay * jitterFactor) + (currentDelay * -jitterFactor)) </pre>
*/
public final class ExponentialBackoff implements LongUnaryOperator {

private static final long DEFAULT_MAX_RETRY_DELAY_MS = 5000;
private static final long DEFAULT_MIN_RETRY_DELAY_MS = 100;

// defaults are taken from gRPC's own exponential backoff policy
private static final double DEFAULT_BACKOFF_FACTOR = 1.6;
private static final double DEFAULT_JITTER_FACTOR = 0.1;

private final long maxDelay;
private final long minDelay;
private final double backoffFactor;
private final double jitterFactor;
private final DoubleSupplier random;

public ExponentialBackoff() {
this(
DEFAULT_MAX_RETRY_DELAY_MS,
DEFAULT_MIN_RETRY_DELAY_MS,
DEFAULT_BACKOFF_FACTOR,
DEFAULT_JITTER_FACTOR);
}

public ExponentialBackoff(final long maxDelay, final long minDelay) {
this(maxDelay, minDelay, DEFAULT_BACKOFF_FACTOR, DEFAULT_JITTER_FACTOR);
}

public ExponentialBackoff(
final long maxDelay,
final long minDelay,
final double backoffFactor,
final double jitterFactor) {
this(
maxDelay,
minDelay,
backoffFactor,
jitterFactor,
() -> ThreadLocalRandom.current().nextDouble());
}

@VisibleForTesting
ExponentialBackoff(
final long maxDelay,
final long minDelay,
final double backoffFactor,
final double jitterFactor,
final DoubleSupplier random) {
this.maxDelay = maxDelay;
this.minDelay = minDelay;
this.backoffFactor = backoffFactor;
this.jitterFactor = jitterFactor;
this.random = random;
}

@Override
public long applyAsLong(final long operand) {
return supplyRetryDelay(operand);
}

public long supplyRetryDelay(final long currentRetryDelay) {
final var delay = Math.max(Math.min(maxDelay, currentRetryDelay * backoffFactor), minDelay);
final var jitter = computeJitter(delay);
return Math.round(delay + jitter);
}

private double computeJitter(final double value) {
final var minFactor = value * -jitterFactor;
final var maxFactor = value * jitterFactor;

return (random.getAsDouble() * (maxFactor - minFactor)) + minFactor;
}
}

0 comments on commit b3d3236

Please sign in to comment.