Skip to content

Commit 7bc2b88

Browse files
vishwasioolegz
authored andcommitted
fix(core): correct retry semantics for consumer maxAttempts configuration
The retry policy previously used maxRetries(properties.getMaxAttempts()), which caused an off-by-one error because maxRetries represents the number of retries while maxAttempts represents total delivery attempts. Example: maxAttempts=2 resulted in 3 executions (1 initial + 2 retries). This change converts maxAttempts to retries by using: maxRetries = maxAttempts - 1 A test (MaxAttemptsRetryTests) has been added to reproduce and verify the correct behavior. Signed-off-by: vishwasio <[email protected]>
1 parent f2b6af6 commit 7bc2b88

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

core/spring-cloud-stream/src/main/java/org/springframework/cloud/stream/binder/AbstractBinder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ protected RetryTemplate buildRetryTemplate(ConsumerProperties properties) {
209209
}
210210
RetryPolicy retryPolicy =
211211
RetryPolicy.builder()
212-
.maxRetries(properties.getMaxAttempts())
212+
.maxRetries(Math.max(0, properties.getMaxAttempts() - 1))
213213
.delay(Duration.ofMillis(properties.getBackOffInitialInterval()))
214214
.multiplier(properties.getBackOffMultiplier())
215215
.maxDelay(Duration.ofMillis(properties.getBackOffMaxInterval()))
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.springframework.cloud.stream.binder;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.springframework.core.retry.RetryTemplate;
5+
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
class MaxAttemptsRetryTests {
11+
12+
@Test
13+
void maxAttemptsShouldNotExceedConfiguredAttempts() {
14+
15+
ConsumerProperties props = new ConsumerProperties();
16+
props.setMaxAttempts(2);
17+
18+
TestBinder binder = new TestBinder();
19+
20+
RetryTemplate retryTemplate = binder.buildRetryTemplate(props);
21+
22+
AtomicInteger attempts = new AtomicInteger();
23+
24+
try {
25+
retryTemplate.execute(() -> {
26+
attempts.incrementAndGet();
27+
throw new RuntimeException("fail");
28+
});
29+
} catch (Exception ignored) {
30+
}
31+
32+
assertThat(attempts.get()).isEqualTo(2);
33+
}
34+
35+
static class TestBinder extends AbstractBinder<Object, ConsumerProperties, ProducerProperties> {
36+
37+
@Override
38+
protected Binding<Object> doBindConsumer(String name, String group, Object inboundBindTarget,
39+
ConsumerProperties consumerProperties) {
40+
return null;
41+
}
42+
43+
@Override
44+
protected Binding<Object> doBindProducer(String name, Object outboundBindTarget,
45+
ProducerProperties producerProperties) {
46+
return null;
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)