From b02272f47a0d3d39853fbf939705ec4b38ee7f98 Mon Sep 17 00:00:00 2001 From: Min Jin Date: Thu, 22 Oct 2026 10:27:28 -0700 Subject: [PATCH] [extended] Fix int overflow in exponential backoff rate limiter ItemExponentialFailureRateLimiter.when() computed the backoff multiplier as 1 << exp, an int shift. Once exp reaches 31 the shift overflows to Integer.MIN_VALUE (and wraps thereafter), so baseDelay.multipliedBy(...) returns a negative Duration whenever maxDelay is large enough that the baseDelay*2^exp branch is still selected at that failure count. Compute the multiplier in long arithmetic (1L << exp), matching the long type of exp and of Duration.multipliedBy(long). Add a regression test that fails before and passes after. --- .../ItemExponentialFailureRateLimiter.java | 2 +- ...ItemExponentialFailureRateLimiterTest.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/extended/src/main/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiter.java b/extended/src/main/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiter.java index 2ebd9e2b92..871a5504bb 100644 --- a/extended/src/main/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiter.java +++ b/extended/src/main/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiter.java @@ -37,7 +37,7 @@ public ItemExponentialFailureRateLimiter(Duration baseDelay, Duration maxDelay) public Duration when(T item) { long exp = failures.computeIfAbsent(item, k -> new AtomicLong()).getAndIncrement(); long d = maxDelay.toMillis() >> exp; - return d > baseDelay.toMillis() ? baseDelay.multipliedBy(1 << exp) : maxDelay; + return d > baseDelay.toMillis() ? baseDelay.multipliedBy(1L << exp) : maxDelay; } @Override diff --git a/extended/src/test/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiterTest.java b/extended/src/test/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiterTest.java index e5d4d214a3..8aa6f6a769 100644 --- a/extended/src/test/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiterTest.java +++ b/extended/src/test/java/io/kubernetes/client/extended/workqueue/ratelimiter/ItemExponentialFailureRateLimiterTest.java @@ -94,4 +94,23 @@ void negativeMaxDelay() { assertThat(rateLimiter.when("one")).isEqualTo(Duration.ofSeconds(-1000)); assertThat(rateLimiter.when("one")).isEqualTo(Duration.ofSeconds(-1000)); } + + @Test + void highFailureCountStaysPositive() { + // maxDelay is large enough that the baseDelay*2^exp branch is still selected at exp == 31. + // The multiplier must be computed in long arithmetic; an int shift (1 << 31) overflows to + // Integer.MIN_VALUE and yields a negative backoff, which is never a valid delay. + Duration baseDelay = Duration.ofMillis(1); + Duration maxDelay = Duration.ofMillis(1L << 40); + RateLimiter rateLimiter = new ItemExponentialFailureRateLimiter<>(baseDelay, maxDelay); + + // Advance to exp == 31 (the 32nd call returns the delay computed with exp == 31). + for (int i = 0; i < 31; i++) { + rateLimiter.when("item"); + } + + Duration backoff = rateLimiter.when("item"); + assertThat(backoff).isPositive(); + assertThat(backoff).isEqualTo(baseDelay.multipliedBy(1L << 31)); + } }