From 78f11f3d2361fb699f54b9d85800ad5645ea6151 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Fri, 11 Sep 2026 15:31:44 +0200 Subject: [PATCH 1/4] Fix continuation cleanup when TimerTask is cancelled --- .../timer/JavaTimerInstrumentation.java | 5 ++--- .../timer/TimerTaskInstrumentation.java | 21 +++++++++++++------ .../timer/TimerTaskContinuationTest.groovy | 6 ++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java index 07e3cc934e7..da5879b9c9d 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java @@ -14,7 +14,6 @@ import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; import datadog.trace.bootstrap.instrumentation.java.concurrent.State; -import java.util.TimerTask; import net.bytebuddy.asm.Advice; public class JavaTimerInstrumentation @@ -40,7 +39,7 @@ public void methodAdvice(MethodTransformer transformer) { public static final class TimerScheduleAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void before(@Advice.Argument(0) TimerTask task, @Advice.Argument(2) long period) { + public static void before(@Advice.Argument(0) Runnable task, @Advice.Argument(2) long period) { // don't propagate fixed time / rate executions if (period != 0) { return; @@ -53,7 +52,7 @@ public static void before(@Advice.Argument(0) TimerTask task, @Advice.Argument(2 } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void after(@Advice.Argument(0) TimerTask task, @Advice.Thrown Throwable thrown) { + public static void after(@Advice.Argument(0) Runnable task, @Advice.Thrown Throwable thrown) { if (null != thrown && !exclude(RUNNABLE, task)) { cancelTask(InstrumentationContext.get(Runnable.class, State.class), task); } diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java index 496b6d20dc8..5e27393cdda 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java @@ -10,30 +10,39 @@ import datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils; import datadog.trace.bootstrap.instrumentation.java.concurrent.State; import datadog.trace.instrumentation.java.concurrent.runnable.RunnableInstrumentation; -import java.util.TimerTask; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; /** - * Instrument {@link java.util.TimerTask} + * Instruments {@code java.util.TimerTask}. * *

Only the cancel part is handled here because the execution is handled by the {@link * RunnableInstrumentation} */ public final class TimerTaskInstrumentation implements Instrumenter.ForBootstrap, - Instrumenter.ForTypeHierarchy, + Instrumenter.CanShortcutTypeMatching, Instrumenter.HasMethodAdvice { + @Override + public boolean onlyMatchKnownTypes() { + return false; + } + + @Override + public String[] knownMatchingTypes() { + return new String[] {"java.util.TimerTask"}; + } + @Override public String hierarchyMarkerType() { - return null; // bootstrap type + return null; } @Override public ElementMatcher hierarchyMatcher() { - return extendsClass(named(TimerTask.class.getName())); + return extendsClass(named("java.util.TimerTask")); } @Override @@ -45,7 +54,7 @@ public void methodAdvice(MethodTransformer transformer) { public static class CancelAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onCancel(@Advice.This TimerTask self) { + public static void onCancel(@Advice.This Runnable self) { AdviceUtils.cancelTask(InstrumentationContext.get(Runnable.class, State.class), self); } } diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy index 0ce840a5600..9bdd855ae34 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy @@ -29,6 +29,12 @@ class TimerTaskContinuationTest extends InstrumentationSpecification { @Override def setup() { timerTask = new TimerTask() { + // The test agent starts after TimerTask is loaded, so expose cancel on the subclass. + @Override + boolean cancel() { + super.cancel() + } + @Override void run() { AgentSpan span = startSpan("test", "child") From da0704f241f65ea907aa6c3afdfa25bc8afc3ae6 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Fri, 11 Sep 2026 15:52:55 +0200 Subject: [PATCH 2/4] Whitelist timertask --- .../agent/tooling/bytebuddy/matcher/ignored_class_name.trie | 1 + .../src/test/groovy/timer/TimerTaskContinuationTest.groovy | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie index 36cedaf6081..e86702c934c 100644 --- a/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie +++ b/dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie @@ -66,6 +66,7 @@ 0 java.nio.ByteBuffer 0 java.rmi.* 0 java.util.Timer +0 java.util.TimerTask 0 java.util.concurrent.* 1 java.util.concurrent.ConcurrentHashMap* 1 java.util.concurrent.atomic.* diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy index 9bdd855ae34..0ce840a5600 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/test/groovy/timer/TimerTaskContinuationTest.groovy @@ -29,12 +29,6 @@ class TimerTaskContinuationTest extends InstrumentationSpecification { @Override def setup() { timerTask = new TimerTask() { - // The test agent starts after TimerTask is loaded, so expose cancel on the subclass. - @Override - boolean cancel() { - super.cancel() - } - @Override void run() { AgentSpan span = startSpan("test", "child") From b270bcb8dfb6977a5fc88fcf7e1e16ea7392b5d4 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Fri, 11 Sep 2026 17:09:17 +0200 Subject: [PATCH 3/4] minimize --- .../timer/JavaTimerInstrumentation.java | 5 +++-- .../timer/TimerTaskInstrumentation.java | 21 ++++++------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java index da5879b9c9d..07e3cc934e7 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/JavaTimerInstrumentation.java @@ -14,6 +14,7 @@ import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; import datadog.trace.bootstrap.instrumentation.java.concurrent.State; +import java.util.TimerTask; import net.bytebuddy.asm.Advice; public class JavaTimerInstrumentation @@ -39,7 +40,7 @@ public void methodAdvice(MethodTransformer transformer) { public static final class TimerScheduleAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void before(@Advice.Argument(0) Runnable task, @Advice.Argument(2) long period) { + public static void before(@Advice.Argument(0) TimerTask task, @Advice.Argument(2) long period) { // don't propagate fixed time / rate executions if (period != 0) { return; @@ -52,7 +53,7 @@ public static void before(@Advice.Argument(0) Runnable task, @Advice.Argument(2) } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) - public static void after(@Advice.Argument(0) Runnable task, @Advice.Thrown Throwable thrown) { + public static void after(@Advice.Argument(0) TimerTask task, @Advice.Thrown Throwable thrown) { if (null != thrown && !exclude(RUNNABLE, task)) { cancelTask(InstrumentationContext.get(Runnable.class, State.class), task); } diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java index 5e27393cdda..496b6d20dc8 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java @@ -10,39 +10,30 @@ import datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils; import datadog.trace.bootstrap.instrumentation.java.concurrent.State; import datadog.trace.instrumentation.java.concurrent.runnable.RunnableInstrumentation; +import java.util.TimerTask; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; /** - * Instruments {@code java.util.TimerTask}. + * Instrument {@link java.util.TimerTask} * *

Only the cancel part is handled here because the execution is handled by the {@link * RunnableInstrumentation} */ public final class TimerTaskInstrumentation implements Instrumenter.ForBootstrap, - Instrumenter.CanShortcutTypeMatching, + Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice { - @Override - public boolean onlyMatchKnownTypes() { - return false; - } - - @Override - public String[] knownMatchingTypes() { - return new String[] {"java.util.TimerTask"}; - } - @Override public String hierarchyMarkerType() { - return null; + return null; // bootstrap type } @Override public ElementMatcher hierarchyMatcher() { - return extendsClass(named("java.util.TimerTask")); + return extendsClass(named(TimerTask.class.getName())); } @Override @@ -54,7 +45,7 @@ public void methodAdvice(MethodTransformer transformer) { public static class CancelAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onCancel(@Advice.This Runnable self) { + public static void onCancel(@Advice.This TimerTask self) { AdviceUtils.cancelTask(InstrumentationContext.get(Runnable.class, State.class), self); } } From 926bb11bbaebec8d98b0f859c39d2169e9785bd7 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Fri, 11 Sep 2026 17:27:28 +0200 Subject: [PATCH 4/4] Avoid to load too early TimerTask --- .../java/concurrent/timer/TimerTaskInstrumentation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java index 496b6d20dc8..9272ff57353 100644 --- a/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java +++ b/dd-java-agent/instrumentation/java/java-concurrent/java-concurrent-1.8/src/main/java/datadog/trace/instrumentation/java/concurrent/timer/TimerTaskInstrumentation.java @@ -33,7 +33,7 @@ public String hierarchyMarkerType() { @Override public ElementMatcher hierarchyMatcher() { - return extendsClass(named(TimerTask.class.getName())); + return extendsClass(named("java.util.TimerTask")); } @Override