Skip to content

Commit 43df701

Browse files
committed
Avoid ThreadLocal memory leak in JoinPointImpl
according to https://rules.sonarsource.com/java/tag/leak/RSPEC-5164/. Now, there no longer is a thread-local stack of AroundClosure instances, but rather a list of them, which can only grow but never shrink. Instead, we now have a thread-local (integer) list index, for every thread being initialised with pointing to the last element. I.e., every thread can unwind by decrementing the index while proceeding, independently of other threads. A positive side effect is that this approach also works for long-lived threads from thread pools, used by executor services. Hence, test Bugs199Tests.testAsyncProceedNestedAroundAdviceThreadPool_gh128, which was previously commented out, has been activated and passes, see #141. I am not sure if this brings @AspectJ style, non-inlined, nested around advice execution functionally on par with native ones, but at least for current scenarios it seems to work. Fixes #288, #141. Signed-off-by: Alexander Kriegisch <[email protected]>
1 parent 966397e commit 43df701

File tree

3 files changed

+23
-35
lines changed

3 files changed

+23
-35
lines changed

runtime/src/main/java/org/aspectj/runtime/reflect/JoinPointImpl.java

+16-29
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313

1414
package org.aspectj.runtime.reflect;
1515

16-
import java.util.Stack;
17-
1816
import org.aspectj.lang.JoinPoint;
1917
import org.aspectj.lang.ProceedingJoinPoint;
2018
import org.aspectj.lang.Signature;
2119
import org.aspectj.lang.reflect.SourceLocation;
2220
import org.aspectj.runtime.internal.AroundClosure;
2321

22+
import java.util.ArrayList;
23+
import java.util.List;
24+
2425
class JoinPointImpl implements ProceedingJoinPoint {
2526
static class StaticPartImpl implements JoinPoint.StaticPart {
2627
String kind;
@@ -79,18 +80,6 @@ public EnclosingStaticPartImpl(int count, String kind, Signature signature, Sour
7980
}
8081
}
8182

82-
static class InheritableThreadLocalAroundClosureStack extends InheritableThreadLocal<Stack<AroundClosure>> {
83-
@Override
84-
protected Stack<AroundClosure> initialValue() {
85-
return new Stack<>();
86-
}
87-
88-
@Override
89-
protected Stack<AroundClosure> childValue(Stack<AroundClosure> parentValue) {
90-
return (Stack<AroundClosure>) parentValue.clone();
91-
}
92-
}
93-
9483
Object _this;
9584
Object target;
9685
Object[] args;
@@ -152,23 +141,26 @@ public final String toLongString() {
152141
// will either be using arc or arcs but not both. arcs being non-null
153142
// indicates it is in use (even if an empty stack)
154143
private AroundClosure arc = null;
155-
private InheritableThreadLocalAroundClosureStack arcs = null;
144+
private List<AroundClosure> arcs = null;
145+
private final ThreadLocal<Integer> arcIndex = ThreadLocal.withInitial(() -> arcs == null ? -1 : arcs.size() - 1);
156146

157147
public void set$AroundClosure(AroundClosure arc) {
158148
this.arc = arc;
159149
}
160150

161-
public void stack$AroundClosure(AroundClosure arc) {
151+
public void stack$AroundClosure(AroundClosure arc) {
162152
// If input parameter arc is null this is the 'unlink' call from AroundClosure
163153
if (arcs == null) {
164-
arcs = new InheritableThreadLocalAroundClosureStack();
154+
arcs = new ArrayList<>();
165155
}
166-
if (arc==null) {
167-
this.arcs.get().pop();
168-
} else {
169-
this.arcs.get().push(arc);
156+
if (arc == null) {
157+
arcIndex.set(arcIndex.get() - 1);
158+
}
159+
else {
160+
this.arcs.add(arc);
161+
arcIndex.set(arcs.size() - 1);
170162
}
171-
}
163+
}
172164

173165
public Object proceed() throws Throwable {
174166
// when called from a before advice, but be a no-op
@@ -179,19 +171,14 @@ public Object proceed() throws Throwable {
179171
return arc.run(arc.getState());
180172
}
181173
} else {
182-
final AroundClosure ac = arcs.get().peek();
174+
final AroundClosure ac = arcs.get(arcIndex.get());
183175
return ac.run(ac.getState());
184176
}
185177
}
186178

187179
public Object proceed(Object[] adviceBindings) throws Throwable {
188180
// when called from a before advice, but be a no-op
189-
AroundClosure ac = null;
190-
if (arcs == null) {
191-
ac = arc;
192-
} else {
193-
ac = arcs.get().peek();
194-
}
181+
AroundClosure ac = arcs == null ? arc : arcs.get(arcIndex.get());
195182

196183
if (ac == null) {
197184
return null;

tests/src/test/java/org/aspectj/systemtest/ajc199/Bugs199Tests.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ public void testAsyncProceedNestedAroundAdvice_gh128() {
4949
}
5050

5151
public void testAsyncProceedNestedAroundAdviceThreadPool_gh128() {
52-
// TODO: future improvement, see https://github.com/eclipse-aspectj/aspectj/issues/141
53-
// runTest("asynchronous proceed for nested around-advice (@AspectJ, thread pool)");
52+
// Test created for #128, but initially commented out and remaining work recorded in #141.
53+
// Now, test is expected to pass. See https://github.com/eclipse-aspectj/aspectj/issues/141.
54+
runTest("asynchronous proceed for nested around-advice (@AspectJ, thread pool)");
5455
}
5556

5657
public void testAsyncProceedNestedAroundAdviceNative_gh128() {

tests/src/test/resources/org/aspectj/systemtest/ajc199/ajc199.xml

+4-4
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
</ajc-test>
197197

198198
<ajc-test dir="bugs199/github_128" title="asynchronous proceed for nested around-advice (@AspectJ)">
199-
<compile files="Application.java MarkerA.java MarkerB.java annotation_syntax/MarkerAAspect.aj annotation_syntax/MarkerBAspect.aj" options="-1.8" />
199+
<compile files="Application.java MarkerA.java MarkerB.java annotation_syntax/MarkerAAspect.aj annotation_syntax/MarkerBAspect.aj" options="-1.8 -XnoInline" />
200200
<run class="Application" options="1,1">
201201
<stdout ordered="no">
202202
<line text=">> Outer intercept"/>
@@ -275,7 +275,7 @@
275275
</ajc-test>
276276

277277
<ajc-test dir="bugs199/github_128" title="asynchronous proceed for nested around-advice (@AspectJ, thread pool)">
278-
<compile files="Application.java MarkerA.java MarkerB.java annotation_syntax/MarkerAAspect.aj annotation_syntax/MarkerBAspect.aj" options="-1.8" />
278+
<compile files="Application.java MarkerA.java MarkerB.java annotation_syntax/MarkerAAspect.aj annotation_syntax/MarkerBAspect.aj" options="-1.8 -XnoInline" />
279279
<run class="Application" options="1,1,true">
280280
<stdout ordered="no">
281281
<line text=">> Outer intercept"/>
@@ -354,7 +354,7 @@
354354
</ajc-test>
355355

356356
<ajc-test dir="bugs199/github_128" title="asynchronous proceed for nested around-advice (native)">
357-
<compile files="Application.java MarkerA.java MarkerB.java native_syntax/MarkerAAspect.aj native_syntax/MarkerBAspect.aj" options="-1.8" />
357+
<compile files="Application.java MarkerA.java MarkerB.java native_syntax/MarkerAAspect.aj native_syntax/MarkerBAspect.aj" options="-1.8 -XnoInline" />
358358
<run class="Application" options="1,1">
359359
<stdout ordered="no">
360360
<line text=">> Outer intercept"/>
@@ -433,7 +433,7 @@
433433
</ajc-test>
434434

435435
<ajc-test dir="bugs199/github_128" title="asynchronous proceed for nested around-advice (native, thread pool)">
436-
<compile files="Application.java MarkerA.java MarkerB.java native_syntax/MarkerAAspect.aj native_syntax/MarkerBAspect.aj" options="-1.8" />
436+
<compile files="Application.java MarkerA.java MarkerB.java native_syntax/MarkerAAspect.aj native_syntax/MarkerBAspect.aj" options="-1.8 -XnoInline" />
437437
<run class="Application" options="1,1,true">
438438
<stdout ordered="no">
439439
<line text=">> Outer intercept"/>

0 commit comments

Comments
 (0)