From 544c86b3282b23d5fabe33dfe54e68047b277b9c Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Mon, 27 Jan 2025 09:26:24 +0100 Subject: [PATCH 1/2] fix(java-concurrent): Fix FJP instrumentation on Java 21+ From Java 21+, do no more stop context capture for ForkJoinPool.execute(Runnable). --- .../StructuredTaskScopeInstrumentation.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/dd-java-agent/instrumentation/java-concurrent/java-concurrent-21/src/main/java/datadog/trace/instrumentation/java/concurrent/structuredconcurrency/StructuredTaskScopeInstrumentation.java b/dd-java-agent/instrumentation/java-concurrent/java-concurrent-21/src/main/java/datadog/trace/instrumentation/java/concurrent/structuredconcurrency/StructuredTaskScopeInstrumentation.java index 4bb5c8f4bf9..5a9787f0b40 100644 --- a/dd-java-agent/instrumentation/java-concurrent/java-concurrent-21/src/main/java/datadog/trace/instrumentation/java/concurrent/structuredconcurrency/StructuredTaskScopeInstrumentation.java +++ b/dd-java-agent/instrumentation/java-concurrent/java-concurrent-21/src/main/java/datadog/trace/instrumentation/java/concurrent/structuredconcurrency/StructuredTaskScopeInstrumentation.java @@ -1,21 +1,16 @@ package datadog.trace.instrumentation.java.concurrent.structuredconcurrency; import static datadog.trace.bootstrap.instrumentation.java.concurrent.AdviceUtils.capture; -import static datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter.ExcludeType.FORK_JOIN_TASK; -import static java.util.Collections.singleton; import static java.util.Collections.singletonMap; import static net.bytebuddy.matcher.ElementMatchers.isConstructor; import com.google.auto.service.AutoService; -import datadog.trace.agent.tooling.ExcludeFilterProvider; import datadog.trace.agent.tooling.Instrumenter; import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.api.Platform; import datadog.trace.bootstrap.ContextStore; import datadog.trace.bootstrap.InstrumentationContext; -import datadog.trace.bootstrap.instrumentation.java.concurrent.ExcludeFilter; import datadog.trace.bootstrap.instrumentation.java.concurrent.State; -import java.util.Collection; import java.util.Map; import net.bytebuddy.asm.Advice; @@ -27,10 +22,7 @@ @SuppressWarnings("unused") @AutoService(InstrumenterModule.class) public class StructuredTaskScopeInstrumentation extends InstrumenterModule.Tracing - implements Instrumenter.ForBootstrap, - Instrumenter.ForSingleType, - Instrumenter.HasMethodAdvice, - ExcludeFilterProvider { + implements Instrumenter.ForBootstrap, Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { public StructuredTaskScopeInstrumentation() { super("java_concurrent", "structured_task_scope"); @@ -57,14 +49,6 @@ public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice(isConstructor(), getClass().getName() + "$ConstructorAdvice"); } - @Override - public Map> excludedClasses() { - // Prevent the ForkJoinPool instrumentation to enable the task scope too early on the carrier - // thread rather than on the expected running thread, which is virtual by default. - return singletonMap( - FORK_JOIN_TASK, singleton("java.util.concurrent.ForkJoinTask$RunnableExecuteAction")); - } - public static final class ConstructorAdvice { @Advice.OnMethodExit public static void captureScope( From b01cedb12e66d06c02bcaf8199c5127dfbe0cab3 Mon Sep 17 00:00:00 2001 From: Bruce Bujon Date: Wed, 12 Mar 2025 17:33:21 +0100 Subject: [PATCH 2/2] feat(java-concurrent): Add smoke tests for Java 21+ concurrent API --- .../concurrent/java-21/build.gradle | 48 +++++++++++++ .../smoketest/concurrent/ConcurrentApp.java | 28 ++++++++ .../concurrent/FibonacciCalculator.java | 0 .../VirtualThreadExecuteCalculator.java | 54 +++++++++++++++ .../VirtualThreadInvokeAllCalculator.java | 50 ++++++++++++++ .../VirtualThreadInvokeAnyCalculator.java | 46 +++++++++++++ ...VirtualThreadSubmitCallableCalculator.java | 47 +++++++++++++ ...VirtualThreadSubmitRunnableCalculator.java | 54 +++++++++++++++ .../concurrent/AbstractConcurrentTest.groovy | 69 +++++++++++++++++++ .../concurrent/VirtualThreadTest.groovy | 61 ++++++++++++++++ .../concurrent/{ => java-8}/build.gradle | 13 +--- .../smoketest/concurrent/ConcurrentApp.java | 6 +- .../concurrent/DemoExecutorService.java | 0 .../DemoForkJoinPoolExternalClient.java | 56 +++++++++++++++ .../concurrent/DemoForkJoinPoolTask.java} | 13 ++-- .../concurrent/FibonacciCalculator.java | 10 +++ .../concurrent}/AbstractDemoTest.groovy | 4 +- .../DemoExecutorServiceTest.groovy | 2 +- .../DemoForkJoinPoolExternalClientTest.groovy | 12 ++++ .../DemoForkJoinPoolTaskTest.groovy | 12 ++++ .../DemoMultipleConcurrenciesTest.groovy | 6 +- .../datadog/smoketest/DemoForkJoinTest.groovy | 12 ---- settings.gradle | 3 +- 23 files changed, 569 insertions(+), 37 deletions(-) create mode 100644 dd-smoke-tests/concurrent/java-21/build.gradle create mode 100644 dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java rename dd-smoke-tests/concurrent/{ => java-21}/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java (100%) create mode 100644 dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadExecuteCalculator.java create mode 100644 dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAllCalculator.java create mode 100644 dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAnyCalculator.java create mode 100644 dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitCallableCalculator.java create mode 100644 dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitRunnableCalculator.java create mode 100644 dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/AbstractConcurrentTest.groovy create mode 100644 dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/VirtualThreadTest.groovy rename dd-smoke-tests/concurrent/{ => java-8}/build.gradle (54%) rename dd-smoke-tests/concurrent/{ => java-8}/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java (78%) rename dd-smoke-tests/concurrent/{ => java-8}/src/main/java/datadog/smoketest/concurrent/DemoExecutorService.java (100%) create mode 100644 dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClient.java rename dd-smoke-tests/concurrent/{src/main/java/datadog/smoketest/concurrent/DemoForkJoin.java => java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolTask.java} (70%) create mode 100644 dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java rename dd-smoke-tests/concurrent/{src/test/groovy/datadog/smoketest => java-8/src/test/groovy/datadog/smoketest/concurrent}/AbstractDemoTest.groovy (96%) rename dd-smoke-tests/concurrent/{src/test/groovy/datadog/smoketest => java-8/src/test/groovy/datadog/smoketest/concurrent}/DemoExecutorServiceTest.groovy (87%) create mode 100644 dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClientTest.groovy create mode 100644 dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolTaskTest.groovy rename dd-smoke-tests/concurrent/{src/test/groovy/datadog/smoketest => java-8/src/test/groovy/datadog/smoketest/concurrent}/DemoMultipleConcurrenciesTest.groovy (60%) delete mode 100644 dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoForkJoinTest.groovy diff --git a/dd-smoke-tests/concurrent/java-21/build.gradle b/dd-smoke-tests/concurrent/java-21/build.gradle new file mode 100644 index 00000000000..147758d8b89 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/build.gradle @@ -0,0 +1,48 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' +} + +ext { + minJavaVersionForTests = JavaVersion.VERSION_21 +} + +apply from: "$rootDir/gradle/java.gradle" + +description = 'JDK 21 Concurrent Integration Tests' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} +tasks.withType(JavaCompile).configureEach { + setJavaVersion(it, 21) + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +// Disable plugin tasks that do not support Java 21: +// * forbiddenApis is missing classes +// * spotless as the google-java-format version does not support Java 21 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 } +} + +application { + mainClassName = 'datadog.smoketest.concurrent.ConcurrentApp' +} + +dependencies { + implementation group: 'io.opentelemetry.instrumentation', name: 'opentelemetry-instrumentation-annotations', version: '2.13.3' + testImplementation project(':dd-smoke-tests') +} + +tasks.withType(Test).configureEach { + dependsOn "shadowJar" + jvmArgs "-Ddatadog.smoketest.shadowJar.path=${tasks.shadowJar.archiveFile.get()}" +} diff --git a/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java new file mode 100644 index 00000000000..aa58e12286d --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java @@ -0,0 +1,28 @@ +package datadog.smoketest.concurrent; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.ExecutionException; + +public class ConcurrentApp { + @WithSpan("main") + public static void main(String[] args) { + for (String arg : args) { + try (FibonacciCalculator calc = getCalculator(arg)) { + calc.computeFibonacci(10); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException("Failed to compute fibonacci number.", e); + } + } + } + + private static FibonacciCalculator getCalculator(String name) { + return switch (name) { + case "virtualThreadExecute" -> new VirtualThreadExecuteCalculator(); + case "virtualThreadSubmitRunnable" -> new VirtualThreadSubmitRunnableCalculator(); + case "virtualThreadSubmitCallable" -> new VirtualThreadSubmitCallableCalculator(); + case "virtualThreadInvokeAll" -> new VirtualThreadInvokeAllCalculator(); + case "virtualThreadInvokeAny" -> new VirtualThreadInvokeAnyCalculator(); + default -> throw new RuntimeException("Unknown Fibonacci calculator: " + name); + }; + } +} diff --git a/dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java similarity index 100% rename from dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java rename to dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java diff --git a/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadExecuteCalculator.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadExecuteCalculator.java new file mode 100644 index 00000000000..119c227f782 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadExecuteCalculator.java @@ -0,0 +1,54 @@ +package datadog.smoketest.concurrent; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class VirtualThreadExecuteCalculator implements FibonacciCalculator { + private final ExecutorService executor; + + public VirtualThreadExecuteCalculator() { + this.executor = Executors.newVirtualThreadPerTaskExecutor(); + } + + @Override + public long computeFibonacci(int n) throws ExecutionException, InterruptedException { + FibonacciExecuteTask task = new FibonacciExecuteTask(n); + this.executor.execute(task); + return task.result.get(); + } + + public class FibonacciExecuteTask implements Runnable { + private final long n; + private final CompletableFuture result; + + public FibonacciExecuteTask(long n) { + this.n = n; + this.result = new CompletableFuture<>(); + } + + @WithSpan("compute") + public void run() { + if (this.n <= 1) { + this.result.complete(this.n); + return; + } + FibonacciExecuteTask task1 = new FibonacciExecuteTask(this.n - 1); + FibonacciExecuteTask task2 = new FibonacciExecuteTask(this.n - 2); + executor.execute(task1); + executor.execute(task2); + try { + this.result.complete(task1.result.get() + task2.result.get()); + } catch (InterruptedException | ExecutionException e) { + this.result.completeExceptionally(e); + } + } + } + + @Override + public void close() { + this.executor.shutdown(); + } +} diff --git a/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAllCalculator.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAllCalculator.java new file mode 100644 index 00000000000..4c8407326eb --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAllCalculator.java @@ -0,0 +1,50 @@ +package datadog.smoketest.concurrent; + +import static java.util.Set.of; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class VirtualThreadInvokeAllCalculator implements FibonacciCalculator { + private final ExecutorService executor; + + public VirtualThreadInvokeAllCalculator() { + this.executor = Executors.newVirtualThreadPerTaskExecutor(); + } + + @Override + public long computeFibonacci(int n) throws ExecutionException, InterruptedException { + FibonacciSubmitTask task = new FibonacciSubmitTask(n); + return this.executor.invokeAll(of(task)).getFirst().get(); + } + + public class FibonacciSubmitTask implements Callable { + private final long n; + + public FibonacciSubmitTask(long n) { + this.n = n; + } + + @WithSpan("compute") + public Long call() throws ExecutionException, InterruptedException { + if (this.n <= 1) { + return this.n; + } + FibonacciSubmitTask task1 = new FibonacciSubmitTask(this.n - 1); + FibonacciSubmitTask task2 = new FibonacciSubmitTask(this.n - 2); + List> futures = executor.invokeAll(List.of(task1, task2)); + return futures.getFirst().get() + futures.getLast().get(); + } + } + + @Override + public void close() { + this.executor.shutdown(); + } +} diff --git a/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAnyCalculator.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAnyCalculator.java new file mode 100644 index 00000000000..c7b9205e273 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadInvokeAnyCalculator.java @@ -0,0 +1,46 @@ +package datadog.smoketest.concurrent; + +import static java.util.Set.of; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class VirtualThreadInvokeAnyCalculator implements FibonacciCalculator { + private final ExecutorService executor; + + public VirtualThreadInvokeAnyCalculator() { + this.executor = Executors.newVirtualThreadPerTaskExecutor(); + } + + @Override + public long computeFibonacci(int n) throws ExecutionException, InterruptedException { + FibonacciSubmitTask task = new FibonacciSubmitTask(n); + return this.executor.invokeAny(of(task)); + } + + public class FibonacciSubmitTask implements Callable { + private final long n; + + public FibonacciSubmitTask(long n) { + this.n = n; + } + + @WithSpan("compute") + public Long call() throws ExecutionException, InterruptedException { + if (this.n <= 1) { + return this.n; + } + FibonacciSubmitTask task1 = new FibonacciSubmitTask(this.n - 1); + FibonacciSubmitTask task2 = new FibonacciSubmitTask(this.n - 2); + return executor.invokeAny(of(task1)) + executor.invokeAny(of(task2)); + } + } + + @Override + public void close() { + this.executor.shutdown(); + } +} diff --git a/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitCallableCalculator.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitCallableCalculator.java new file mode 100644 index 00000000000..6281287b413 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitCallableCalculator.java @@ -0,0 +1,47 @@ +package datadog.smoketest.concurrent; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +public class VirtualThreadSubmitCallableCalculator implements FibonacciCalculator { + private final ExecutorService executor; + + public VirtualThreadSubmitCallableCalculator() { + this.executor = Executors.newVirtualThreadPerTaskExecutor(); + } + + @Override + public long computeFibonacci(int n) throws ExecutionException, InterruptedException { + FibonacciSubmitTask task = new FibonacciSubmitTask(n); + return this.executor.submit(task).get(); + } + + public class FibonacciSubmitTask implements Callable { + private final long n; + + public FibonacciSubmitTask(long n) { + this.n = n; + } + + @WithSpan("compute") + public Long call() throws ExecutionException, InterruptedException { + if (this.n <= 1) { + return this.n; + } + FibonacciSubmitTask task1 = new FibonacciSubmitTask(this.n - 1); + FibonacciSubmitTask task2 = new FibonacciSubmitTask(this.n - 2); + Future future1 = executor.submit(task1); + Future future2 = executor.submit(task2); + return future1.get() + future2.get(); + } + } + + @Override + public void close() { + this.executor.shutdown(); + } +} diff --git a/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitRunnableCalculator.java b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitRunnableCalculator.java new file mode 100644 index 00000000000..d1a0940b4d7 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/main/java/datadog/smoketest/concurrent/VirtualThreadSubmitRunnableCalculator.java @@ -0,0 +1,54 @@ +package datadog.smoketest.concurrent; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class VirtualThreadSubmitRunnableCalculator implements FibonacciCalculator { + private final ExecutorService executor; + + public VirtualThreadSubmitRunnableCalculator() { + this.executor = Executors.newVirtualThreadPerTaskExecutor(); + } + + @Override + public long computeFibonacci(int n) throws ExecutionException, InterruptedException { + FibonacciSubmitTask task = new FibonacciSubmitTask(n); + this.executor.execute(task); + return task.result.get(); + } + + public class FibonacciSubmitTask implements Runnable { + private final long n; + private final CompletableFuture result; + + public FibonacciSubmitTask(long n) { + this.n = n; + this.result = new CompletableFuture<>(); + } + + @WithSpan("compute") + public void run() { + if (this.n <= 1) { + this.result.complete(this.n); + return; + } + FibonacciSubmitTask task1 = new FibonacciSubmitTask(this.n - 1); + FibonacciSubmitTask task2 = new FibonacciSubmitTask(this.n - 2); + executor.submit(task1); + executor.submit(task2); + try { + this.result.complete(task1.result.get() + task2.result.get()); + } catch (InterruptedException | ExecutionException e) { + this.result.completeExceptionally(e); + } + } + } + + @Override + public void close() { + this.executor.shutdown(); + } +} diff --git a/dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/AbstractConcurrentTest.groovy b/dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/AbstractConcurrentTest.groovy new file mode 100644 index 00000000000..b6e8b9d1b61 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/AbstractConcurrentTest.groovy @@ -0,0 +1,69 @@ +package datadog.smoketest.concurrent + +import datadog.smoketest.AbstractSmokeTest +import datadog.trace.test.agent.decoder.DecodedTrace + +import java.util.function.Function + +import static java.util.concurrent.TimeUnit.SECONDS + +abstract class AbstractConcurrentTest extends AbstractSmokeTest { + protected static final int TIMEOUT_SECS = 10 + protected abstract List getTestArguments() + + @Override + ProcessBuilder createProcessBuilder() { + def jarPath = System.getProperty("datadog.smoketest.shadowJar.path") + def command = new ArrayList() + command.add(javaPath()) + command.addAll(defaultJavaProperties) + command.add("-Ddd.trace.otel.enabled=true") + command.addAll(["-jar", jarPath]) + command.addAll(getTestArguments()) + + ProcessBuilder processBuilder = new ProcessBuilder(command) + processBuilder.directory(new File(buildDirectory)) + } + + @Override + Closure decodedTracesCallback() { + return {} // force traces decoding + } + + protected static Function checkTrace() { + return { + trace -> + // Check for 'main' span + def mainSpan = trace.spans.find { it.name == 'main' } + if (!mainSpan) { + return false + } + // Check that there are only 'main' and 'compute' spans + def otherSpans = trace.spans.findAll { it.name != 'main' && it.name != 'compute' } + if (!otherSpans.isEmpty()) { + return false + } + // Check that every 'compute' span is in the same trace and is either a child of the 'main' span or another 'compute' span + def computeSpans = trace.spans.findAll { it.name == 'compute' } + if (computeSpans.isEmpty()) { + return false + } + return computeSpans.every { + if (it.traceId != mainSpan.traceId) { + return false + } + if (it.parentId != mainSpan.spanId && trace.spans.find(s -> s.spanId == it.parentId).name != 'compute') { + return false + } + return true + } + } + } + + protected void receivedCorrectTrace() { + waitForTrace(defaultPoll, checkTrace()) + assert traceCount.get() == 1 + assert testedProcess.waitFor(TIMEOUT_SECS, SECONDS) + assert testedProcess.exitValue() == 0 + } +} diff --git a/dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/VirtualThreadTest.groovy b/dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/VirtualThreadTest.groovy new file mode 100644 index 00000000000..a3903ba9dc8 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-21/src/test/groovy/datadog/smoketest/concurrent/VirtualThreadTest.groovy @@ -0,0 +1,61 @@ +package datadog.smoketest.concurrent + +class VirtualThreadExecuteTest extends AbstractConcurrentTest { + @Override + protected List getTestArguments() { + return ['virtualThreadExecute'] + } + + def 'test VirtualThread execute runnable'() { + expect: + receivedCorrectTrace() + } +} + +class VirtualThreadInvokeAllTest extends AbstractConcurrentTest { + @Override + protected List getTestArguments() { + return ['virtualThreadInvokeAll'] + } + + def 'test VirtualThread invokeAll callable'() { + expect: + receivedCorrectTrace() + } +} + +class VirtualThreadInvokeAnyTest extends AbstractConcurrentTest { + @Override + protected List getTestArguments() { + return ['virtualThreadInvokeAny'] + } + + def 'test VirtualThread invoke any callable'() { + expect: + receivedCorrectTrace() + } +} + +class VirtualThreadSubmitRunnableTest extends AbstractConcurrentTest { + @Override + protected List getTestArguments() { + return ['virtualThreadSubmitRunnable'] + } + + def 'test VirtualThread submit runnable'() { + expect: + receivedCorrectTrace() + } +} + +class VirtualThreadSubmitCallableTest extends AbstractConcurrentTest { + @Override + protected List getTestArguments() { + return ['virtualThreadSubmitCallable'] + } + + def 'test VirtualThread submit callable'() { + expect: + receivedCorrectTrace() + } +} diff --git a/dd-smoke-tests/concurrent/build.gradle b/dd-smoke-tests/concurrent/java-8/build.gradle similarity index 54% rename from dd-smoke-tests/concurrent/build.gradle rename to dd-smoke-tests/concurrent/java-8/build.gradle index b95668127e6..2afc5b0a712 100644 --- a/dd-smoke-tests/concurrent/build.gradle +++ b/dd-smoke-tests/concurrent/java-8/build.gradle @@ -1,32 +1,23 @@ plugins { - id 'java' id 'application' id 'com.github.johnrengelman.shadow' } apply from: "$rootDir/gradle/java.gradle" -description = 'Concurrent Integration Tests.' +description = 'JDK 8 Concurrent Integration Tests' application { mainClassName = 'datadog.smoketest.concurrent.ConcurrentApp' } dependencies { - implementation('io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.13.1') - implementation project(':dd-trace-api') + implementation group: 'io.opentelemetry.instrumentation', name: 'opentelemetry-instrumentation-annotations', version: '2.13.3' testImplementation project(':dd-smoke-tests') - - testImplementation platform('org.junit:junit-bom:5.10.0') - testImplementation 'org.junit.jupiter:junit-jupiter' } -test { - useJUnitPlatform() -} tasks.withType(Test).configureEach { dependsOn "shadowJar" - jvmArgs "-Ddatadog.smoketest.shadowJar.path=${tasks.shadowJar.archiveFile.get()}" } diff --git a/dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java similarity index 78% rename from dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java rename to dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java index c17d84a8887..c54fb188db3 100644 --- a/dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java +++ b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/ConcurrentApp.java @@ -19,8 +19,10 @@ public static void main(String[] args) { private static FibonacciCalculator getCalculator(String name) { if (name.equalsIgnoreCase("executorService")) { return new DemoExecutorService(); - } else if (name.equalsIgnoreCase("forkJoin")) { - return new DemoForkJoin(); + } else if (name.equalsIgnoreCase("forkJoinPoolTask")) { + return new DemoForkJoinPoolTask(); + } else if (name.equalsIgnoreCase("forkJoinPoolExternalClient")) { + return new DemoForkJoinPoolExternalClient(); } throw new IllegalArgumentException("Unknown calculator: " + name); } diff --git a/dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/DemoExecutorService.java b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoExecutorService.java similarity index 100% rename from dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/DemoExecutorService.java rename to dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoExecutorService.java diff --git a/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClient.java b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClient.java new file mode 100644 index 00000000000..873ac885b48 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClient.java @@ -0,0 +1,56 @@ +package datadog.smoketest.concurrent; + +import io.opentelemetry.instrumentation.annotations.WithSpan; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; + +/** Test ForkJoinPool using the external client API. */ +public class DemoForkJoinPoolExternalClient implements FibonacciCalculator { + private final ForkJoinPool forkJoinPool; + + public DemoForkJoinPoolExternalClient() { + this.forkJoinPool = new ForkJoinPool(); + } + + @Override + public long computeFibonacci(int n) throws ExecutionException, InterruptedException { + FibonacciTask task = new FibonacciTask(n); + this.forkJoinPool.execute(task); + return task.result.get(); + } + + private class FibonacciTask implements Runnable { + private final int n; + private final CompletableFuture result; + + public FibonacciTask(int n) { + this.n = n; + this.result = new CompletableFuture<>(); + } + + @WithSpan("compute") + @Override + public void run() { + if (this.n <= 1) { + this.result.complete((long) this.n); + return; + } + FibonacciTask taskOne = new FibonacciTask(this.n - 1); + forkJoinPool.execute(taskOne); + FibonacciTask taskTwo = new FibonacciTask(this.n - 2); + forkJoinPool.submit(taskTwo); + + try { + this.result.complete(taskOne.result.get() + taskTwo.result.get()); + } catch (InterruptedException | ExecutionException e) { + this.result.completeExceptionally(e); + } + } + } + + @Override + public void close() { + forkJoinPool.shutdown(); + } +} diff --git a/dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/DemoForkJoin.java b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolTask.java similarity index 70% rename from dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/DemoForkJoin.java rename to dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolTask.java index dd77f86f324..486e3e308bd 100644 --- a/dd-smoke-tests/concurrent/src/main/java/datadog/smoketest/concurrent/DemoForkJoin.java +++ b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/DemoForkJoinPoolTask.java @@ -4,19 +4,20 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; -public class DemoForkJoin implements FibonacciCalculator { +/** Test ForkJoinPool using the FJP task API. */ +public class DemoForkJoinPoolTask implements FibonacciCalculator { private final ForkJoinPool forkJoinPool; - public DemoForkJoin() { + public DemoForkJoinPoolTask() { forkJoinPool = new ForkJoinPool(); } @Override public long computeFibonacci(int n) { - return forkJoinPool.invoke(new FibonacciTask(n)); + return new FibonacciTask(n).invoke(); } - private class FibonacciTask extends RecursiveTask { + private static class FibonacciTask extends RecursiveTask { private final int n; public FibonacciTask(int n) { @@ -26,8 +27,8 @@ public FibonacciTask(int n) { @WithSpan("compute") @Override protected Long compute() { - if (n <= 1) { - return (long) n; + if (this.n <= 1) { + return (long) this.n; } FibonacciTask taskOne = new FibonacciTask(n - 1); taskOne.fork(); diff --git a/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java new file mode 100644 index 00000000000..73974d7b135 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-8/src/main/java/datadog/smoketest/concurrent/FibonacciCalculator.java @@ -0,0 +1,10 @@ +package datadog.smoketest.concurrent; + +import java.util.concurrent.ExecutionException; + +public interface FibonacciCalculator extends AutoCloseable { + long computeFibonacci(int n) throws ExecutionException, InterruptedException; + + @Override + void close(); +} diff --git a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/AbstractDemoTest.groovy b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/AbstractDemoTest.groovy similarity index 96% rename from dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/AbstractDemoTest.groovy rename to dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/AbstractDemoTest.groovy index 1628d0459df..dbe05774867 100644 --- a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/AbstractDemoTest.groovy +++ b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/AbstractDemoTest.groovy @@ -1,4 +1,6 @@ -package datadog.smoketest +package datadog.smoketest.concurrent + +import datadog.smoketest.AbstractSmokeTest import static java.util.concurrent.TimeUnit.SECONDS import datadog.trace.test.agent.decoder.DecodedTrace diff --git a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoExecutorServiceTest.groovy b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoExecutorServiceTest.groovy similarity index 87% rename from dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoExecutorServiceTest.groovy rename to dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoExecutorServiceTest.groovy index b99891b89bb..f27ad653f32 100644 --- a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoExecutorServiceTest.groovy +++ b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoExecutorServiceTest.groovy @@ -1,4 +1,4 @@ -package datadog.smoketest +package datadog.smoketest.concurrent class DemoExecutorServiceTest extends AbstractDemoTest { protected List getTestArguments() { diff --git a/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClientTest.groovy b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClientTest.groovy new file mode 100644 index 00000000000..173d502d941 --- /dev/null +++ b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolExternalClientTest.groovy @@ -0,0 +1,12 @@ +package datadog.smoketest.concurrent + +class DemoForkJoinPoolExternalClientTest extends AbstractDemoTest { + protected List getTestArguments() { + return ["forkJoinPoolExternalClient"] + } + + def 'receive one correct trace when using ForkJoinPool external client API'() { + expect: + receivedCorrectTrace() + } +} diff --git a/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolTaskTest.groovy b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolTaskTest.groovy new file mode 100644 index 00000000000..d1c5618551f --- /dev/null +++ b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoForkJoinPoolTaskTest.groovy @@ -0,0 +1,12 @@ +package datadog.smoketest.concurrent + +class DemoForkJoinPoolTaskTest extends AbstractDemoTest { + protected List getTestArguments() { + return ["forkJoinPoolTask"] + } + + def 'receive one correct trace when using ForkJoinPool task API'() { + expect: + receivedCorrectTrace() + } +} diff --git a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoMultipleConcurrenciesTest.groovy b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoMultipleConcurrenciesTest.groovy similarity index 60% rename from dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoMultipleConcurrenciesTest.groovy rename to dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoMultipleConcurrenciesTest.groovy index 06ec5dc67b2..ce374c66a60 100644 --- a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoMultipleConcurrenciesTest.groovy +++ b/dd-smoke-tests/concurrent/java-8/src/test/groovy/datadog/smoketest/concurrent/DemoMultipleConcurrenciesTest.groovy @@ -1,11 +1,11 @@ -package datadog.smoketest +package datadog.smoketest.concurrent class DemoMultipleConcurrenciesTest extends AbstractDemoTest { protected List getTestArguments() { - return ["executorService", "forkJoin"] + return ["executorService", "forkJoinPoolTask"] } - def 'receive one correct trace when using multiple concurrency strategies (ExecutorService and ForkJoin)'() { + def 'receive one correct trace when using multiple concurrency strategies (ExecutorService and ForkJoinPool task API)'() { expect: receivedCorrectTrace() } diff --git a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoForkJoinTest.groovy b/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoForkJoinTest.groovy deleted file mode 100644 index 00994a6ef4f..00000000000 --- a/dd-smoke-tests/concurrent/src/test/groovy/datadog/smoketest/DemoForkJoinTest.groovy +++ /dev/null @@ -1,12 +0,0 @@ -package datadog.smoketest - -class DemoForkJoinTest extends AbstractDemoTest { - protected List getTestArguments() { - return ["forkJoin"] - } - - def 'receive one correct trace when using ForkJoin'() { - expect: - receivedCorrectTrace() - } -} diff --git a/settings.gradle b/settings.gradle index f63786d4d28..4df68ccfc00 100644 --- a/settings.gradle +++ b/settings.gradle @@ -96,7 +96,8 @@ include ':dd-smoke-tests:apm-tracing-disabled' include ':dd-smoke-tests:armeria-grpc' include ':dd-smoke-tests:backend-mock' include ':dd-smoke-tests:cli' -include ':dd-smoke-tests:concurrent' +include ':dd-smoke-tests:concurrent:java-8' +include ':dd-smoke-tests:concurrent:java-21' include ':dd-smoke-tests:crashtracking' include ':dd-smoke-tests:custom-systemloader' include ':dd-smoke-tests:dynamic-config'