1818 */
1919package co .elastic .apm .agent .concurrent ;
2020
21+ import co .elastic .apm .agent .collections .WeakConcurrentProviderImpl ;
2122import co .elastic .apm .agent .impl .Tracer ;
2223import co .elastic .apm .agent .impl .transaction .AbstractSpan ;
2324import co .elastic .apm .agent .sdk .DynamicTransformer ;
2425import co .elastic .apm .agent .sdk .ElasticApmInstrumentation ;
2526import co .elastic .apm .agent .sdk .state .GlobalState ;
26- import co .elastic .apm .agent .sdk .weakconcurrent .WeakConcurrent ;
2727import co .elastic .apm .agent .sdk .weakconcurrent .WeakMap ;
2828
2929import javax .annotation .Nullable ;
4141@ GlobalState
4242public class JavaConcurrent {
4343
44- private static final WeakMap <Object , AbstractSpan <?>> contextMap = WeakConcurrent .buildMap ();
44+ private static final WeakMap <Object , AbstractSpan <?>> contextMap = WeakConcurrentProviderImpl .createWeakSpanMap ();
45+
4546 private static final List <Class <? extends ElasticApmInstrumentation >> RUNNABLE_CALLABLE_FJTASK_INSTRUMENTATION = Collections .
4647 <Class <? extends ElasticApmInstrumentation >>singletonList (RunnableCallableForkJoinTaskInstrumentation .class );
4748 static final ThreadLocal <Boolean > needsContext = new ThreadLocal <>();
@@ -57,10 +58,7 @@ public class JavaConcurrent {
5758 }
5859
5960 private static void removeContext (Object o ) {
60- AbstractSpan <?> context = contextMap .remove (o );
61- if (context != null ) {
62- context .decrementReferences ();
63- }
61+ contextMap .remove (o );
6462 }
6563
6664 private static boolean shouldAvoidContextPropagation (@ Nullable Object executable ) {
@@ -69,21 +67,34 @@ private static boolean shouldAvoidContextPropagation(@Nullable Object executable
6967 needsContext .get () == Boolean .FALSE ;
7068 }
7169
70+ /**
71+ * Retrieves the context mapped to the provided task and activates it on the current thread.
72+ * It is the responsibility of the caller to deactivate the returned context at the right time.
73+ * If the mapped context is already the active span of this thread, this method returns {@code null}.
74+ * @param o a task for which running there may be a context to activate
75+ * @param tracer the tracer
76+ * @return the context mapped to the provided task or {@code null} if such does not exist or if the mapped context
77+ * is already the active one on the current thread.
78+ */
7279 @ Nullable
7380 public static AbstractSpan <?> restoreContext (Object o , Tracer tracer ) {
7481 // When an Executor executes directly on the current thread we need to enable this thread for context propagation again
7582 needsContext .set (Boolean .TRUE );
76- AbstractSpan <?> context = contextMap .remove (o );
83+
84+ // we cannot remove yet, as this decrements the reference count, which may cause already ended spans to be recycled ahead of time
85+ AbstractSpan <?> context = contextMap .get (o );
7786 if (context == null ) {
7887 return null ;
7988 }
80- if (tracer .getActive () != context ) {
81- context .activate ();
82- context .decrementReferences ();
83- return context ;
84- } else {
85- context .decrementReferences ();
86- return null ;
89+
90+ try {
91+ if (tracer .getActive () != context ) {
92+ return context .activate ();
93+ } else {
94+ return null ;
95+ }
96+ } finally {
97+ contextMap .remove (o );
8798 }
8899 }
89100
@@ -110,7 +121,6 @@ public static Runnable withContext(@Nullable Runnable runnable, Tracer tracer) {
110121 private static void captureContext (Object task , AbstractSpan <?> active ) {
111122 DynamicTransformer .ensureInstrumented (task .getClass (), RUNNABLE_CALLABLE_FJTASK_INSTRUMENTATION );
112123 contextMap .put (task , active );
113- active .incrementReferences ();
114124 // Do no discard branches leading to async operations so not to break span references
115125 active .setNonDiscardable ();
116126 }
0 commit comments