Skip to content

Commit f083fd3

Browse files
Fixed Virtual thread instrumentation.
1 parent 21795b3 commit f083fd3

2 files changed

Lines changed: 30 additions & 4 deletions

File tree

  • dd-java-agent/instrumentation/java/java-concurrent

dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/executor/ExecutorModule.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ public Map<String, String> contextStore() {
5252
RUNNABLE,
5353
Arrays.asList(
5454
"datadog.trace.bootstrap.instrumentation.java.concurrent.Wrapper",
55-
"datadog.trace.bootstrap.instrumentation.java.concurrent.ComparableRunnable"));
55+
"datadog.trace.bootstrap.instrumentation.java.concurrent.ComparableRunnable",
56+
// VirtualThread context must be activated on mount/unmount, not on Runnable.run().
57+
// If Runnable instrumentation also activates it, the scope can attach to carrier
58+
// threads and fight with the dedicated virtual-thread instrumentation.
59+
"java.lang.VirtualThread"));
5660
map.put(
5761
EXECUTOR,
5862
Collections.singletonList("org.apache.mina.filter.executor.OrderedThreadPoolExecutor"));

dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-21.0/src/main/java/datadog/trace/instrumentation/java/concurrent/virtualthread/TaskRunnerInstrumentation.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.capture;
55
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.endTaskScope;
66
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.startTaskScope;
7-
import static java.util.Collections.singletonMap;
87
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
98
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
109

@@ -15,7 +14,10 @@
1514
import datadog.trace.bootstrap.InstrumentationContext;
1615
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
1716
import datadog.trace.bootstrap.instrumentation.java.concurrent.State;
17+
import java.util.Collections;
18+
import java.util.HashMap;
1819
import java.util.Map;
20+
import java.util.concurrent.RunnableFuture;
1921
import net.bytebuddy.asm.Advice;
2022
import net.bytebuddy.asm.Advice.OnMethodEnter;
2123
import net.bytebuddy.asm.Advice.OnMethodExit;
@@ -44,7 +46,13 @@ public boolean isEnabled() {
4446

4547
@Override
4648
public Map<String, String> contextStore() {
47-
return singletonMap("java.lang.Runnable", State.class.getName());
49+
Map<String, String> stores = new HashMap<>(2);
50+
stores.put(Runnable.class.getName(), State.class.getName());
51+
// TaskRunner may wrap a RunnableFuture that already has captured context.
52+
// Expose this store so Construct can reuse that existing state and avoid creating
53+
// a second continuation for the same logical task.
54+
stores.put(RunnableFuture.class.getName(), State.class.getName());
55+
return Collections.unmodifiableMap(stores);
4856
}
4957

5058
@Override
@@ -55,7 +63,21 @@ public void methodAdvice(MethodTransformer transformer) {
5563

5664
public static final class Construct {
5765
@OnMethodExit(suppress = Throwable.class)
58-
public static void captureScope(@Advice.This Runnable task) {
66+
public static void captureScope(
67+
@Advice.This Runnable task, @Advice.FieldValue("task") Runnable innerTask) {
68+
if (innerTask instanceof RunnableFuture) {
69+
// `submit(...)` creates a FutureTask and then wraps it in TaskRunner.
70+
// The FutureTask constructor is already instrumented and captures continuation once.
71+
// Reuse the same State so we don't capture a second continuation here.
72+
State innerState =
73+
InstrumentationContext.get(RunnableFuture.class, State.class)
74+
.get((RunnableFuture<?>) innerTask);
75+
if (innerState != null) {
76+
InstrumentationContext.get(Runnable.class, State.class).put(task, innerState);
77+
return;
78+
}
79+
}
80+
// Plain execute(Runnable) path where there is no wrapped RunnableFuture state to reuse.
5981
capture(InstrumentationContext.get(Runnable.class, State.class), task);
6082
}
6183
}

0 commit comments

Comments
 (0)