diff --git a/components/environment/build.gradle.kts b/components/environment/build.gradle.kts index 8d9febfe679..7818fef2463 100644 --- a/components/environment/build.gradle.kts +++ b/components/environment/build.gradle.kts @@ -24,7 +24,7 @@ val excludedClassesCoverage by extra { "datadog.environment.JavaVirtualMachine.JvmOptionsHolder", // depends on OS and JVM vendor "datadog.environment.JvmOptions", // depends on OS and JVM vendor "datadog.environment.OperatingSystem**", // depends on OS - "datadog.environment.ThreadUtils", // depends on JVM version + "datadog.environment.ThreadSupport", // requires Java 21 ) } val excludedClassesBranchCoverage by extra { diff --git a/components/environment/src/main/java/datadog/environment/ThreadSupport.java b/components/environment/src/main/java/datadog/environment/ThreadSupport.java new file mode 100644 index 00000000000..861215ca837 --- /dev/null +++ b/components/environment/src/main/java/datadog/environment/ThreadSupport.java @@ -0,0 +1,142 @@ +package datadog.environment; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Helper class for working with {@link Thread}s. + * + *
Uses feature detection and provides static helpers to work with different versions of Java. + * + *
This class is designed to use {@link MethodHandle}s that constant propagate to minimize the
+ * overhead.
+ */
+public final class ThreadSupport {
+ static final MethodHandle THREAD_ID_MH = findThreadIdMethodHandle();
+ static final MethodHandle IS_VIRTUAL_MH = findIsVirtualMethodHandle();
+ static final MethodHandle NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR_MH =
+ findNewVirtualThreadPerTaskExecutorMethodHandle();
+
+ private ThreadSupport() {}
+
+ /**
+ * Provides the best identifier available for the current {@link Thread}. Uses {@link
+ * Thread#threadId()} on 19+ or {@link Thread#getId()} on older JVMs.
+ *
+ * @return The best identifier available for the current {@link Thread}.
+ */
+ public static long threadId() {
+ return threadId(Thread.currentThread());
+ }
+
+ /**
+ * Provides the best identifier available for the given {@link Thread}. Uses {@link
+ * Thread#threadId()} on 19+ or {@link Thread#getId()} on older JVMs.
+ *
+ * @return The best identifier available for the given {@link Thread}.
+ */
+ public static long threadId(Thread thread) {
+ if (THREAD_ID_MH != null) {
+ try {
+ return (long) THREAD_ID_MH.invoke(thread);
+ } catch (Throwable ignored) {
+ }
+ }
+ return thread.getId();
+ }
+
+ /**
+ * Checks whether virtual threads are supported on this JVM.
+ *
+ * @return {@code true} if virtual threads are supported, {@code false} otherwise.
+ */
+ public static boolean supportsVirtualThreads() {
+ return (IS_VIRTUAL_MH != null);
+ }
+
+ /**
+ * Checks whether the current thread is a virtual thread.
+ *
+ * @return {@code true} if the current thread is a virtual thread, {@code false} otherwise.
+ */
+ public static boolean isVirtual() {
+ // IS_VIRTUAL_MH will constant propagate -- then dead code eliminate -- and inline as needed
+ return IS_VIRTUAL_MH != null && isVirtual(Thread.currentThread());
+ }
+
+ /**
+ * Checks whether the given thread is a virtual thread.
+ *
+ * @param thread The thread to check.
+ * @return {@code true} if the given thread is virtual, {@code false} otherwise.
+ */
+ public static boolean isVirtual(Thread thread) {
+ // IS_VIRTUAL_MH will constant propagate -- then dead code eliminate -- and inline as needed
+ if (IS_VIRTUAL_MH != null) {
+ try {
+ return (boolean) IS_VIRTUAL_MH.invoke(thread);
+ } catch (Throwable ignored) {
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the virtual thread per task executor if available.
+ *
+ * @return The virtual thread per task executor if available wrapped into an {@link Optional}, or
+ * {@link Optional#empty()} otherwise.
+ */
+ public static Optional Uses feature detection and provides static helpers to work with different versions of Java
- *
- * This class is designed to use MethodHandles that constant propagate to minimize the overhead
- */
-public final class ThreadUtils {
- static final MethodHandle H_IS_VIRTUAL = lookupIsVirtual();
- static final MethodHandle H_ID = lookupId();
-
- private ThreadUtils() {}
-
- /** Provides the best id available for the Thread Uses threadId on 19+; getId on older JVMs */
- public static final long threadId(Thread thread) {
- try {
- return (long) H_ID.invoke(thread);
- } catch (Throwable t) {
- return 0L;
- }
- }
-
- /** Indicates whether virtual threads are supported on this JVM */
- public static final boolean supportsVirtualThreads() {
- return (H_IS_VIRTUAL != null);
- }
-
- /** Indicates if the current thread is a virtual thread */
- public static final boolean isCurrentThreadVirtual() {
- // H_IS_VIRTUAL will constant propagate -- then dead code eliminate -- and inline as needed
- try {
- return (H_IS_VIRTUAL != null) && (boolean) H_IS_VIRTUAL.invoke(Thread.currentThread());
- } catch (Throwable t) {
- return false;
- }
- }
-
- /** Indicates if the provided thread is a virtual thread */
- public static final boolean isVirtual(Thread thread) {
- // H_IS_VIRTUAL will constant propagate -- then dead code eliminate -- and inline as needed
- try {
- return (H_IS_VIRTUAL != null) && (boolean) H_IS_VIRTUAL.invoke(thread);
- } catch (Throwable t) {
- return false;
- }
- }
-
- private static final MethodHandle lookupIsVirtual() {
- try {
- return MethodHandles.lookup()
- .findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class));
- } catch (NoSuchMethodException | IllegalAccessException e) {
- return null;
- }
- }
-
- private static final MethodHandle lookupId() {
- MethodHandle threadIdHandle = lookupThreadId();
- return threadIdHandle != null ? threadIdHandle : lookupGetId();
- }
-
- private static final MethodHandle lookupThreadId() {
- try {
- return MethodHandles.lookup()
- .findVirtual(Thread.class, "threadId", MethodType.methodType(long.class));
- } catch (NoSuchMethodException | IllegalAccessException e) {
- return null;
- }
- }
-
- private static final MethodHandle lookupGetId() {
- try {
- return MethodHandles.lookup()
- .findVirtual(Thread.class, "getId", MethodType.methodType(long.class));
- } catch (NoSuchMethodException | IllegalAccessException e) {
- return null;
- }
- }
-}
diff --git a/components/environment/src/test/java/datadog/environment/ThreadSupportTest.java b/components/environment/src/test/java/datadog/environment/ThreadSupportTest.java
new file mode 100644
index 00000000000..be2032c5470
--- /dev/null
+++ b/components/environment/src/test/java/datadog/environment/ThreadSupportTest.java
@@ -0,0 +1,82 @@
+package datadog.environment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.condition.JRE.JAVA_21;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledOnJre;
+
+class ThreadSupportTest {
+ private static ExecutorService singleThreadExecutor;
+ private static ExecutorService newVirtualThreadPerTaskExecutor;
+
+ @BeforeAll
+ static void beforeAll() {
+ singleThreadExecutor = Executors.newSingleThreadExecutor();
+ newVirtualThreadPerTaskExecutor = ThreadSupport.newVirtualThreadPerTaskExecutor().orElse(null);
+ }
+
+ @Test
+ public void testThreadId() throws InterruptedException {
+ AtomicLong threadId = new AtomicLong();
+ Thread thread = new Thread(() -> threadId.set(ThreadSupport.threadId()), "foo");
+ thread.start();
+ try {
+ // always works on Thread's where getId isn't overridden by child class
+ assertEquals(thread.getId(), ThreadSupport.threadId(thread));
+ } finally {
+ thread.join();
+ }
+ assertEquals(thread.getId(), threadId.get());
+ }
+
+ @Test
+ void testSupportsVirtualThreads() {
+ assertEquals(
+ JavaVirtualMachine.isJavaVersionAtLeast(21),
+ ThreadSupport.supportsVirtualThreads(),
+ "expected virtual threads support status");
+ }
+
+ @Test
+ void testPlatformThread() {
+ assertVirtualThread(singleThreadExecutor, false);
+ }
+
+ @Test
+ @EnabledOnJre(JAVA_21)
+ void testVirtualThread() {
+ assertVirtualThread(newVirtualThreadPerTaskExecutor, true);
+ }
+
+ static void assertVirtualThread(ExecutorService executorService, boolean expected) {
+ Future