Skip to content

Commit 803af48

Browse files
committed
Fix scope leak in ObservedAspect.
Removed 'scope.close' call from the future completion thread; added new test case ObservedAspectTests#parentObservationScopeShouldNotLeakToFutureCompletionThread. Signed-off-by: pema4 <[email protected]>
1 parent d595959 commit 803af48

File tree

2 files changed

+36
-5
lines changed

2 files changed

+36
-5
lines changed

micrometer-observation/src/main/java/io/micrometer/observation/aop/ObservedAspect.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,16 @@ public ObservedAspect(ObservationRegistry registry,
147147
try {
148148
Object result = pjp.proceed();
149149
if (result == null) {
150-
stopObservation(observation, scope, null);
150+
stopObservation(observation, null);
151151
return result;
152152
}
153153
else {
154154
CompletionStage<?> stage = (CompletionStage<?>) result;
155-
return stage.whenComplete((res, error) -> stopObservation(observation, scope, error));
155+
return stage.whenComplete((res, error) -> stopObservation(observation, error));
156156
}
157157
}
158158
catch (Throwable error) {
159-
stopObservation(observation, scope, error);
159+
stopObservation(observation, error);
160160
throw error;
161161
}
162162
finally {
@@ -187,11 +187,10 @@ private Method getMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
187187
return method;
188188
}
189189

190-
private void stopObservation(Observation observation, Observation.Scope scope, @Nullable Throwable error) {
190+
private void stopObservation(Observation observation, @Nullable Throwable error) {
191191
if (error != null) {
192192
observation.error(error);
193193
}
194-
scope.close();
195194
observation.stop();
196195
}
197196

samples/micrometer-samples-spring-framework6/src/test/java/io/micrometer/samples/spring6/aop/ObservedAspectTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,26 @@ void annotatedAsyncCallShouldBeObservedAndErrorRecorded() {
169169
.isEqualTo(simulatedException);
170170
}
171171

172+
@Test
173+
void observationShouldNotLeakToFutureCompletionThread() {
174+
registry.observationConfig().observationHandler(new ObservationTextPublisher());
175+
176+
AspectJProxyFactory pf = new AspectJProxyFactory(new ObservedService());
177+
pf.addAspect(new ObservedAspect(registry));
178+
179+
ObservedService service = pf.getProxy();
180+
FakeAsyncTask fakeAsyncTask = new FakeAsyncTask("test-result");
181+
Executor sameThreadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
182+
CompletableFuture<String> asyncResult = service
183+
.parent(() -> service.asyncChild(fakeAsyncTask, sameThreadExecutor));
184+
CompletableFuture<Void> asyncAssertion = asyncResult
185+
.thenRunAsync(() -> assertThat(registry).doesNotHaveAnyRemainingCurrentObservation(), sameThreadExecutor);
186+
fakeAsyncTask.proceed();
187+
fakeAsyncTask.get();
188+
189+
assertThat(asyncAssertion).succeedsWithin(Duration.ofMillis(200));
190+
}
191+
172192
@Test
173193
void customObservationConventionShouldBeUsed() {
174194
registry.observationConfig().observationHandler(new ObservationTextPublisher());
@@ -401,6 +421,18 @@ CompletableFuture<String> async(FakeAsyncTask fakeAsyncTask) {
401421
contextSnapshot.wrapExecutor(Executors.newSingleThreadExecutor()));
402422
}
403423

424+
@Observed(name = "test.async-child")
425+
CompletableFuture<String> asyncChild(FakeAsyncTask fakeAsyncTask, Executor singleThreadExecutor) {
426+
System.out.println("async-child");
427+
return CompletableFuture.supplyAsync(fakeAsyncTask, singleThreadExecutor);
428+
}
429+
430+
@Observed(name = "test.parent")
431+
<T> T parent(Supplier<T> supplier) {
432+
System.out.println("parent");
433+
return supplier.get();
434+
}
435+
404436
}
405437

406438
interface TestBeanInterface {

0 commit comments

Comments
 (0)