From b611fcf114a0d1a6cf51c37ca04a117fce960daa Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:02:49 +0200 Subject: [PATCH] Use double division to calculate applied jitter in ExponentialBackOff In order to avoid the staircase scaling effect that results from our current use of integer division, this commit revises applyJitter(long) in ExponentialBackOffExecution to use floating-point (double) division to calculate the applied jitter. Closes gh-36943 --- .../util/backoff/ExponentialBackOff.java | 2 +- .../util/ExponentialBackOffTests.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java b/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java index e60484b8d16..39d2e77a441 100644 --- a/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java +++ b/spring-core/src/main/java/org/springframework/util/backoff/ExponentialBackOff.java @@ -317,7 +317,7 @@ public class ExponentialBackOff implements BackOff { long initialInterval = getInitialInterval(); // When initialInterval is 0 the interval never grows, so the scale factor // stays at its baseline value of 1 and the full configured jitter is applied. - long applicableJitter = jitter * (initialInterval > 0 ? (interval / initialInterval) : 1); + long applicableJitter = (long) (jitter * (initialInterval > 0 ? ((double) interval / initialInterval) : 1)); long min = Math.max(interval - applicableJitter, initialInterval); long max = Math.min(interval + applicableJitter, getMaxInterval()); return min + (long) (Math.random() * (max - min)); diff --git a/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java b/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java index 50999afc589..c8e0cd920f2 100644 --- a/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java +++ b/spring-core/src/test/java/org/springframework/util/ExponentialBackOffTests.java @@ -131,6 +131,21 @@ class ExponentialBackOffTests { assertThatNoException().isThrownBy(execution::nextBackOff); } + @Test // gh-36943 + void jitterScalesProportionallyWithInterval() { + ExponentialBackOff backOff = new ExponentialBackOff(); + backOff.setJitter(100); + BackOffExecution execution = backOff.start(); + + // Default: initialInterval=2000, multiplier=1.5 + // Attempt 1: interval=2000, scale=1.0, applicableJitter=100 → [max(1900,2000), 2100) = [2000, 2099] + assertThat(execution.nextBackOff()).isBetween(2000L, 2099L); + // Attempt 2: interval=3000, scale=1.5, applicableJitter=150 → [max(2850,2000), 3150) = [2850, 3149] + assertThat(execution.nextBackOff()).isBetween(2850L, 3149L); + // Attempt 3: interval=4500, scale=2.25, applicableJitter=225 → [max(4275,2000), 4725) = [4275, 4724] + assertThat(execution.nextBackOff()).isBetween(4275L, 4724L); + } + @Test void maxIntervalReachedImmediately() { ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0);