From 61201db704882101548c354ff0a79810969c55e7 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:45:15 +0100 Subject: [PATCH] Improve error message for preemptive timeout in RetryTemplate The error message in such cases now indicates that the retry process is being aborted preemptively due to pending sleep time. For example: Retry policy for operation 'myMethod' would exceed timeout (5 ms) due to pending sleep time (10 ms); preemptively aborting execution See gh-35963 --- .../org/springframework/core/retry/RetryTemplate.java | 8 ++++++-- .../springframework/core/retry/RetryTemplateTests.java | 5 ++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java b/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java index 95b024e8370..c0f5163a45b 100644 --- a/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java +++ b/spring-core/src/main/java/org/springframework/core/retry/RetryTemplate.java @@ -213,9 +213,13 @@ public class RetryTemplate implements RetryOperations { // would be if we were to sleep for sleepTime milliseconds. long elapsedTime = System.currentTimeMillis() + sleepTime - startTime; if (elapsedTime >= timeout) { - RetryException retryException = new RetryException( + String message = (sleepTime > 0 ? """ + Retry policy for operation '%s' would exceed timeout (%d ms) due \ + to pending sleep time (%d ms); preemptively aborting execution""" + .formatted(retryable.getName(), timeout, sleepTime) : "Retry policy for operation '%s' exceeded timeout (%d ms); aborting execution" - .formatted(retryable.getName(), timeout), exceptions.removeLast()); + .formatted(retryable.getName(), timeout)); + RetryException retryException = new RetryException(message, exceptions.removeLast()); exceptions.forEach(retryException::addSuppressed); this.retryListener.onRetryPolicyTimeout(this.retryPolicy, retryable, retryException); throw retryException; diff --git a/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java b/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java index dd1190afc04..88adcb62478 100644 --- a/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java +++ b/spring-core/src/test/java/org/springframework/core/retry/RetryTemplateTests.java @@ -502,7 +502,10 @@ class RetryTemplateTests { assertThat(invocationCount).hasValue(0); assertThatExceptionOfType(RetryException.class) .isThrownBy(() -> retryTemplate.execute(retryable)) - .withMessageMatching("Retry policy for operation '.+?' exceeded timeout \\(5 ms\\); aborting execution") + .withMessageMatching(""" + Retry policy for operation '.+?' would exceed timeout \\(5 ms\\) \ + due to pending sleep time \\(10 ms\\); preemptively aborting execution\ + """) .withCause(new CustomException("Boom 1")) .satisfies(throwable -> inOrder.verify(retryListener).onRetryPolicyTimeout( eq(retryPolicy), eq(retryable), eq(throwable)));