Browse Source

Fix disabling virtual thread context propagation (#10881)

Lauri Tulmin 11 months ago
parent
commit
e38093ce3f

+ 2 - 1
instrumentation/executors/bootstrap/src/main/java/io/opentelemetry/javaagent/bootstrap/executors/ExecutorAdviceHelper.java

@@ -35,7 +35,8 @@ public final class ExecutorAdviceHelper {
     propagationDisabled.remove();
   }
 
-  private static boolean isPropagationDisabled() {
+  // visible for testing
+  public static boolean isPropagationDisabled() {
     return propagationDisabled.get() != null;
   }
 

+ 6 - 1
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<Test>().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"
     )

+ 63 - 0
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;
+      }
+    }
+  }
+}

+ 1 - 0
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/ignore/GlobalIgnoredTypesConfigurer.java

@@ -76,6 +76,7 @@ public class GlobalIgnoredTypesConfigurer implements IgnoredTypesConfigurer {
         // 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.