diff --git a/instrumentation/executors/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/executors/ExecutorAdviceHelper.java b/instrumentation/executors/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/executors/ExecutorAdviceHelper.java index 7892157b9aa0..5471179df34d 100644 --- a/instrumentation/executors/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/executors/ExecutorAdviceHelper.java +++ b/instrumentation/executors/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/executors/ExecutorAdviceHelper.java @@ -35,7 +35,8 @@ public static void enablePropagation() { propagationDisabled.remove(); } - private static boolean isPropagationDisabled() { + // visible for testing + public static boolean isPropagationDisabled() { return propagationDisabled.get() != null; } diff --git a/instrumentation/executors/javaagent/build.gradle.kts b/instrumentation/executors/javaagent/build.gradle.kts index b0cbe7a3a918..65fd4332209a 100644 --- a/instrumentation/executors/javaagent/build.gradle.kts +++ b/instrumentation/executors/javaagent/build.gradle.kts @@ -13,8 +13,8 @@ dependencies { bootstrap(project(":instrumentation:executors:bootstrap")) testImplementation(project(":instrumentation:executors:testing")) - testImplementation("org.scala-lang:scala-library:2.11.12") + testCompileOnly(project(":instrumentation:executors:bootstrap")) } testing { @@ -29,6 +29,7 @@ testing { dependencies { implementation(project(":instrumentation:executors:testing")) + compileOnly(project(":instrumentation:executors:bootstrap")) } targets { @@ -44,6 +45,10 @@ testing { tasks { withType().configureEach { + // needed for VirtualThreadTest on jdk21 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + jvmArgs( "-Dotel.instrumentation.executors.include=io.opentelemetry.javaagent.instrumentation.executors.ExecutorInstrumentationTest\$CustomThreadPoolExecutor,io.opentelemetry.javaagent.instrumentation.executors.ThreadPoolExecutorTest\$RunnableCheckingThreadPoolExecutor" ) diff --git a/instrumentation/executors/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/executors/VirtualThreadTest.java b/instrumentation/executors/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/executors/VirtualThreadTest.java new file mode 100644 index 000000000000..d89e3493936d --- /dev/null +++ b/instrumentation/executors/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/executors/VirtualThreadTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.executors; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.javaagent.bootstrap.executors.ExecutorAdviceHelper; +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +@EnabledForJreRange(min = JRE.JAVA_21) +class VirtualThreadTest { + + @Test + void testDisableContextPropagation() throws Exception { + TestRunnable testRunnable = new TestRunnable(); + // Thread.ofVirtual().start(testRunnable); + Method ofVirtualMethod = Thread.class.getMethod("ofVirtual"); + Object virtualThreadBuilder = ofVirtualMethod.invoke(null); + Method startVirtualThread = + Class.forName("java.lang.Thread$Builder").getMethod("start", Runnable.class); + Thread thread = (Thread) startVirtualThread.invoke(virtualThreadBuilder, testRunnable); + thread.join(); + + assertThat(testRunnable.error).isNull(); + assertThat(testRunnable.isPropagationDisabled.get()).isTrue(); + } + + private static void executeOnCarrierThread(Callable callable) throws Exception { + // call VirtualThread.executeOnCarrierThread, VirtualThreadInstrumentation disables context + // propagation inside that method + Method executeOnCarrierThreadMethod = + Class.forName("java.lang.VirtualThread") + .getDeclaredMethod("executeOnCarrierThread", Callable.class); + executeOnCarrierThreadMethod.setAccessible(true); + executeOnCarrierThreadMethod.invoke(Thread.currentThread(), callable); + } + + static class TestRunnable implements Runnable { + AtomicBoolean isPropagationDisabled = new AtomicBoolean(); + Exception error; + + @Override + public void run() { + try { + executeOnCarrierThread( + () -> { + isPropagationDisabled.set(ExecutorAdviceHelper.isPropagationDisabled()); + return null; + }); + } catch (Exception exception) { + error = exception; + } + } + } +} diff --git a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/GlobalIgnoredTypesConfigurer.java b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/GlobalIgnoredTypesConfigurer.java index cb0b109c0d32..12a178a5c7ce 100644 --- a/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/GlobalIgnoredTypesConfigurer.java +++ b/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/GlobalIgnoredTypesConfigurer.java @@ -76,6 +76,7 @@ private static void configureIgnoredTypes(IgnoredTypesBuilder builder) { // java.lang.ClassCircularityError: java/lang/ClassLoader$1 // when SecurityManager is enabled. ClassLoader$1 is used in ClassLoader.checkPackageAccess .ignoreClass("java.lang.ClassLoader$") + .allowClass("java.lang.VirtualThread") .allowClass("java.lang.invoke.InnerClassLambdaMetafactory") // Concurrent instrumentation modifies the structure of // Cleaner class incompatibly with java9+ modules.