Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
package datadog.trace.instrumentation.java.concurrent.structuredconcurrency;
package datadog.trace.instrumentation.java.concurrent.structuredconcurrency21;

import static datadog.environment.JavaVirtualMachine.isJavaVersionBetween;
import static datadog.trace.bootstrap.InstrumentationContext.get;
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.capture;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;

import com.google.auto.service.AutoService;
import datadog.environment.JavaVirtualMachine;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.java.concurrent.State;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.OnMethodExit;
import net.bytebuddy.asm.Advice.This;

/**
* This instrumentation captures the active span scope at StructuredTaskScope task creation
* (SubtaskImpl). The scope is then activate and close through the Runnable instrumentation
* (SubtaskImpl implementation Runnable).
* (SubtaskImpl). The scope is then activate and close through the {@link Runnable} instrumentation
* (SubtaskImpl implementing {@link Runnable}).
*/
@SuppressWarnings("unused")
@AutoService(InstrumenterModule.class)
public class StructuredTaskScopeInstrumentation extends InstrumenterModule.Tracing
public class StructuredTaskScope21Instrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForBootstrap, Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public StructuredTaskScopeInstrumentation() {
super("java_concurrent", "structured_task_scope");
public StructuredTaskScope21Instrumentation() {
super("java_concurrent", "structured-task-scope", "structured-task-scope-21");
}

@Override
Expand All @@ -35,13 +36,12 @@ public String instrumentedType() {

@Override
public boolean isEnabled() {
return JavaVirtualMachine.isJavaVersionAtLeast(21) && super.isEnabled();
return isJavaVersionBetween(21, 25) && super.isEnabled();
}

@Override
public Map<String, String> contextStore() {
return singletonMap(
"java.util.concurrent.StructuredTaskScope.SubtaskImpl", State.class.getName());
return singletonMap(Runnable.class.getName(), State.class.getName());
}

@Override
Expand All @@ -50,15 +50,17 @@ public void methodAdvice(MethodTransformer transformer) {
}

public static final class ConstructorAdvice {
@Advice.OnMethodExit
public static <T> void captureScope(
@Advice.This Object task // StructuredTaskScope.SubtaskImpl (can't use the type)
) {
ContextStore<Object, State> contextStore =
InstrumentationContext.get(
"java.util.concurrent.StructuredTaskScope.SubtaskImpl",
"datadog.trace.bootstrap.instrumentation.java.concurrent.State");
capture(contextStore, task);
/**
* Captures task scope to be restored at the start of VirtualThread.run() method by {@link
* Runnable} instrumentation.
*
* @param subTaskImpl The StructuredTaskScope.SubtaskImpl object (the advice are compile against
* Java 8 so the type from JDK25 can't be referred, using {@link Object} instead
*/
Comment thread
AlexeyKuznetsov-DD marked this conversation as resolved.
Outdated
@OnMethodExit(suppress = Throwable.class)
public static void captureScope(@This Object subTaskImpl) {
ContextStore<Runnable, State> contextStore = get(Runnable.class, State.class);
capture(contextStore, (Runnable) subTaskImpl);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apply from: "$rootDir/gradle/java.gradle"

muzzle {
pass {
coreJdk('25')
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package datadog.trace.instrumentation.java.concurrent.structuredconcurrency25;

import static datadog.environment.JavaVirtualMachine.isJavaVersionAtLeast;
import static datadog.trace.bootstrap.InstrumentationContext.get;
import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.capture;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.bootstrap.ContextStore;
import datadog.trace.bootstrap.instrumentation.java.concurrent.State;
import net.bytebuddy.asm.Advice.OnMethodExit;
import net.bytebuddy.asm.Advice.This;

// WARNING:
// This instrumentation is tested using smoke tests as instrumented tests cannot run using Java 25.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add description of WHY it cannot run?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly I did not keep track of all the failures.
The first immediate one that come to mind would be that our Groovy version won't run using Java 25 but there are more reasons. Would it make to document them as our goal next quarter will be to get rid of all of this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the groovy / spock rationale to the comment then 👍

// Instrumented tests rely on Spock / Groovy which cannot run using Java 25 due to byte-code
// compatibility. Check dd-java-agent/instrumentation/java-concurrent/java-concurrent-25 for this
// instrumentation test suite.

/**
* This instrumentation captures the active span scope at StructuredTaskScope task creation
* (SubtaskImpl). The scope is then activate and close through the {@link Runnable} instrumentation
* (SubtaskImpl implementing {@link Runnable}).
*/
@SuppressWarnings("unused")
@AutoService(InstrumenterModule.class)
public class StructuredTaskScope25Instrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForBootstrap, Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public StructuredTaskScope25Instrumentation() {
super("java_concurrent", "structured-task-scope", "structured-task-scope-25");
}

@Override
public String instrumentedType() {
return "java.util.concurrent.StructuredTaskScopeImpl$SubtaskImpl";
}

@Override
public boolean isEnabled() {
return isJavaVersionAtLeast(25) && super.isEnabled();
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(isConstructor(), getClass().getName() + "$ConstructorAdvice");
}

public static final class ConstructorAdvice {
/**
* Captures task scope to be restored at the start of VirtualThread.run() method by {@link
* Runnable} instrumentation.
*
* @param subTaskImpl The StructuredTaskScopeImpl.SubtaskImpl object (the advice are compile
* against Java 8 so the type from JDK25 can't be referred, using {@link Object} instead
*/
@OnMethodExit(suppress = Throwable.class)
public static void captureScope(@This Object subTaskImpl) {
ContextStore<Runnable, State> contextStore = get(Runnable.class, State.class);
capture(contextStore, (Runnable) subTaskImpl);
}
}
}
1 change: 1 addition & 0 deletions dd-smoke-tests/concurrent/java-21/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {

ext {
minJavaVersionForTests = JavaVersion.VERSION_21
maxJavaVersionForTests = JavaVersion.VERSION_25
}

apply from: "$rootDir/gradle/java.gradle"
Expand Down
62 changes: 62 additions & 0 deletions dd-smoke-tests/concurrent/java-25/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
plugins {
id 'application'
id 'com.gradleup.shadow'
}

ext {
// This smoke test should be limited to Java 25 and above
// But the groovy testing framework cannot run on Java 25
// Using Java 8 for now and runs a JVM 25 when forking tests process.
// Relying on forked JVM 25 is hardcoded in the test suites (see createProcessBuilder()).
minJavaVersionForTests = JavaVersion.VERSION_1_8
maxJavaVersionForTests = JavaVersion.VERSION_1_8
}

apply from: "$rootDir/gradle/java.gradle"

description = 'JDK 25 Concurrent Integration Tests'

tasks.named('compileJava').configure {
setJavaVersion(it, 25)
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
options.compilerArgs.add("--enable-preview")
javaToolchains {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
}
}

tasks.named('compileTestGroovy').configure {
setJavaVersion(it, 8)
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

// Disable plugin tasks that do not support Java 25:
// * forbiddenApis is missing classes
// * spotless as the google-java-format version does not support Java 25 and can't be changed once applied
// * spotbugs failed to read class using newer bytecode versions
forbiddenApisMain {
failOnMissingClasses = false
}
['spotlessApply', 'spotlessCheck', 'spotlessJava', 'spotbugsMain'].each {
tasks.named(it).configure { enabled = false }
}
Comment on lines 39 to 48
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bric3 if we decouple Gradle JDK this should be fixed?
@PerfectSlayer Maybe we should try to use forbiddenApi v3.9?
I can see this PR Update ASM to version 9.8 for support of Java 25 bytecode; add signatures and build support for Java 24 GA

Copy link
Copy Markdown
Contributor Author

@PerfectSlayer PerfectSlayer Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, you can try to update it and I can test it there to see if I can active it in this module.
For info, the :java-21 module is also disabling those plugins as not working with 21 either 😞


application {
mainClass = 'datadog.smoketest.concurrent.ConcurrentApp'
}

dependencies {
implementation group: 'io.opentelemetry.instrumentation', name: 'opentelemetry-instrumentation-annotations', version: '2.19.0'
testImplementation project(':dd-smoke-tests')
}

tasks.withType(Test).configureEach {
dependsOn "shadowJar"
jvmArgs "-Ddatadog.smoketest.shadowJar.path=${tasks.shadowJar.archiveFile.get()}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package datadog.smoketest.concurrent;

public class ConcurrentApp {
void main(String[] args) {
if (args.length != 1) {
System.err.println("Missing test case argument");
System.exit(1);
}
String name = args[0];
try {
fromName(name).run();
} catch (InterruptedException e) {
throw new RuntimeException("Failed to test case " + name, e);
}
}

private static TestCase fromName(String name) {
return switch (name) {
case "NestedTasks" -> new NestedTasks();
case "MultipleTasks" -> new MultipleTasks();
case "SimpleCallableTask" -> new SimpleCallableTask();
case "SimpleRunnableTask" -> new SimpleRunnableTask();
default -> throw new IllegalArgumentException("Invalid test case name " + name);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package datadog.smoketest.concurrent;

import io.opentelemetry.instrumentation.annotations.WithSpan;
import java.util.concurrent.StructuredTaskScope;

public final class MultipleTasks implements TestCase {
@WithSpan("parent")
public void run() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
scope.fork(this::runnableTask1);
scope.fork(this::callableTask2);
scope.fork(this::runnableTask3);
scope.join();
}
}

@WithSpan("child1")
void runnableTask1() {
// Some basic computations here
}

@WithSpan("child2")
Boolean callableTask2() {
// Some basic computations here
return true;
}

@WithSpan("child3")
void runnableTask3() {
// Some basic computations here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package datadog.smoketest.concurrent;

import io.opentelemetry.instrumentation.annotations.WithSpan;
import java.util.concurrent.StructuredTaskScope;

public final class NestedTasks implements TestCase {
@WithSpan("parent")
public void run() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
scope.fork(this::task1);
scope.fork(this::task2);
scope.join();
}
}

@WithSpan("child1")
void task1() {
try (var scope = StructuredTaskScope.open()) {
scope.fork(this::subTask1);
scope.fork(this::subTask2);
scope.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

@WithSpan("child2")
void task2() {
// Some basic computations here
}

@WithSpan("great-child1-1")
void subTask1() {
// Some basic computations here
}
@WithSpan("great-child1-2")
void subTask2() {
// Some basic computations here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package datadog.smoketest.concurrent;

import io.opentelemetry.instrumentation.annotations.WithSpan;
import java.util.concurrent.StructuredTaskScope;

public final class SimpleCallableTask implements TestCase {
@WithSpan("parent")
public void run() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
scope.fork(this::doSomething);
scope.join();
}
}

@WithSpan("child")
Boolean doSomething() {
// Some basic computations here
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package datadog.smoketest.concurrent;

import io.opentelemetry.instrumentation.annotations.WithSpan;
import java.util.concurrent.StructuredTaskScope;

public final class SimpleRunnableTask implements TestCase {
@WithSpan("parent")
public void run() throws InterruptedException {
try (var scope = StructuredTaskScope.open()) {
scope.fork(this::doSomething);
scope.join();
}
}

@WithSpan("child")
void doSomething() {
// Some basic computations here
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package datadog.smoketest.concurrent;

@FunctionalInterface
public interface TestCase {
void run() throws InterruptedException;
}
Loading