Skip to content

Commit f92c90a

Browse files
committed
GH-477: Fix MetricsRetryListener.close() for concurrent calls
Fixes: #477 The `Timer.Builder` from Micrometer does not create a new `Builder` instance for its `tags()` call. So, using shared `Timer.Builder` is not OK when it can be used from concurrent calls. * Remove shared `retryMeterProvider` property and use fresh `Timer.Builder` instance in the `MetricsRetryListener.close()`
1 parent 78bd8d2 commit f92c90a

File tree

2 files changed

+24
-8
lines changed

2 files changed

+24
-8
lines changed

Diff for: src/main/java/org/springframework/retry/support/MetricsRetryListener.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ public class MetricsRetryListener implements RetryListener {
6666
private final Map<RetryContext, Timer.Sample> retryContextToSample = Collections
6767
.synchronizedMap(new IdentityHashMap<>());
6868

69-
private final Timer.Builder retryMeterProvider;
70-
7169
private Tags customTags = Tags.empty();
7270

7371
private Function<RetryContext, Iterable<Tag>> customTagsProvider = retryContext -> Tags.empty();
@@ -79,7 +77,6 @@ public class MetricsRetryListener implements RetryListener {
7977
public MetricsRetryListener(MeterRegistry meterRegistry) {
8078
Assert.notNull(meterRegistry, "'meterRegistry' must not be null");
8179
this.meterRegistry = meterRegistry;
82-
this.retryMeterProvider = Timer.builder(TIMER_NAME).description("Metrics for Spring RetryTemplate");
8380
}
8481

8582
/**
@@ -122,7 +119,11 @@ public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T
122119
.and(this.customTagsProvider.apply(context))
123120
.and("exception", throwable != null ? throwable.getClass().getSimpleName() : "none");
124121

125-
sample.stop(this.retryMeterProvider.tags(retryTags).register(this.meterRegistry));
122+
Timer.Builder timeBuilder = Timer.builder(TIMER_NAME)
123+
.description("Metrics for Spring RetryTemplate")
124+
.tags(retryTags);
125+
126+
sample.stop(timeBuilder.register(this.meterRegistry));
126127
}
127128

128129
}

Diff for: src/test/java/org/springframework/retry/support/RetryMetricsTests.java

+19-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.retry.support;
1818

19+
import java.util.concurrent.CompletableFuture;
20+
1921
import io.micrometer.core.instrument.MeterRegistry;
2022
import io.micrometer.core.instrument.Tags;
2123
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
@@ -27,6 +29,7 @@
2729
import org.springframework.retry.RetryException;
2830
import org.springframework.retry.annotation.EnableRetry;
2931
import org.springframework.retry.annotation.Retryable;
32+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
3033
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
3134

3235
import static org.assertj.core.api.Assertions.assertThat;
@@ -48,10 +51,20 @@ public class RetryMetricsTests {
4851

4952
@Test
5053
void metricsAreCollectedForRetryable() {
51-
assertThatNoException().isThrownBy(this.service::service1);
52-
assertThatNoException().isThrownBy(this.service::service1);
53-
assertThatNoException().isThrownBy(this.service::service2);
54-
assertThatExceptionOfType(RetryException.class).isThrownBy(this.service::service3);
54+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
55+
executor.setCorePoolSize(4);
56+
executor.afterPropertiesSet();
57+
58+
CompletableFuture<?> future1 = executor
59+
.submitCompletable(() -> assertThatNoException().isThrownBy(this.service::service1));
60+
CompletableFuture<?> future2 = executor
61+
.submitCompletable(() -> assertThatNoException().isThrownBy(this.service::service1));
62+
CompletableFuture<?> future3 = executor
63+
.submitCompletable(() -> assertThatNoException().isThrownBy(this.service::service2));
64+
CompletableFuture<?> future4 = executor.submitCompletable(
65+
() -> assertThatExceptionOfType(RetryException.class).isThrownBy(this.service::service3));
66+
67+
CompletableFuture.allOf(future1, future2, future3, future4).join();
5568

5669
assertThat(this.meterRegistry.get(MetricsRetryListener.TIMER_NAME)
5770
.tags(Tags.of("name", "org.springframework.retry.support.RetryMetricsTests$Service.service1", "retry.count",
@@ -70,6 +83,8 @@ void metricsAreCollectedForRetryable() {
7083
"3", "exception", "RetryException"))
7184
.timer()
7285
.count()).isEqualTo(1);
86+
87+
executor.destroy();
7388
}
7489

7590
@Configuration(proxyBeanMethods = false)

0 commit comments

Comments
 (0)