Avoid divide-by-zero in ExponentialBackOff jitter

When an ExponentialBackOff is configured with an initialInterval of 0
and a positive jitter, the first nextBackOff() evaluated (jitter *
(interval / initialInterval)) performs integer division by zero
and throws an ArithmeticException.

Both initialInterval = 0 and jitter > 0 are individually accepted
configurations -- with jitter = 0, an initialInterval of 0 already
yields a delay of 0 -- so the combination should not throw.

This commit addresses that by guarding the division so that no jitter
scaling is applied when initialInterval is 0, leaving the behavior for
positive intervals unchanged.

Closes gh-36932

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
This commit is contained in:
junhyeong9812
2026-06-17 12:09:30 +02:00
committed by Sam Brannen
parent 136f78ebd0
commit 924849f55b
2 changed files with 16 additions and 1 deletions
@@ -311,7 +311,7 @@ public class ExponentialBackOff implements BackOff {
long jitter = getJitter();
if (jitter > 0) {
long initialInterval = getInitialInterval();
long applicableJitter = jitter * (interval / initialInterval);
long applicableJitter = jitter * (initialInterval > 0 ? (interval / initialInterval) : 1);
long min = Math.max(interval - applicableJitter, initialInterval);
long max = Math.min(interval + applicableJitter, getMaxInterval());
return min + (long) (Math.random() * (max - min));
@@ -27,6 +27,7 @@ import org.springframework.util.backoff.ExponentialBackOff;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNoException;
/**
* Tests for {@link ExponentialBackOff}.
@@ -118,6 +119,20 @@ class ExponentialBackOffTests {
assertThatIllegalArgumentException().isThrownBy(() -> backOff.setMultiplier(0.9));
}
@Test
void jitterWithZeroInitialInterval() {
// 'initialInterval = 0' and 'jitter > 0' are both individually accepted
// configurations, so their combination must not throw. With initialInterval
// of 0, the first nextBackOff() previously evaluated 'jitter * (0 / 0)',
// resulting in an integer division by zero.
ExponentialBackOff backOff = new ExponentialBackOff();
backOff.setInitialInterval(0);
backOff.setJitter(100);
BackOffExecution execution = backOff.start();
assertThatNoException().isThrownBy(execution::nextBackOff);
}
@Test
void maxIntervalReachedImmediately() {
ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0);