Skip to content

Commit aebe07e

Browse files
committed
feat(env): Add support for virtual thread detection
1 parent 30451d7 commit aebe07e

File tree

3 files changed

+144
-0
lines changed

3 files changed

+144
-0
lines changed

components/environment/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ val excludedClassesCoverage by extra {
2424
"datadog.environment.JavaVirtualMachine.JvmOptionsHolder", // depends on OS and JVM vendor
2525
"datadog.environment.JvmOptions", // depends on OS and JVM vendor
2626
"datadog.environment.OperatingSystem**", // depends on OS
27+
"datadog.environment.ThreadSupport", // requires Java 21
2728
)
2829
}
2930
val excludedClassesBranchCoverage by extra {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package datadog.environment;
2+
3+
import java.lang.invoke.MethodHandle;
4+
import java.lang.invoke.MethodHandles;
5+
import java.lang.invoke.MethodType;
6+
import java.util.Optional;
7+
import java.util.concurrent.ExecutorService;
8+
import java.util.concurrent.Executors;
9+
10+
public final class ThreadSupport {
11+
static final MethodHandle MH_IS_VIRTUAL = findIsVirtualMethodHandle();
12+
static final MethodHandle MH_NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR =
13+
findNewVirtualThreadPerTaskExecutorMethodHandle();
14+
15+
private ThreadSupport() {}
16+
17+
/**
18+
* Checks whether the current thread is a virtual thread.
19+
*
20+
* @return {@code true} if the current thread is a virtual thread, {@code false} otherwise.
21+
*/
22+
public static boolean isVirtual() {
23+
if (MH_IS_VIRTUAL == null) {
24+
return false;
25+
}
26+
return isVirtual(Thread.currentThread());
27+
}
28+
29+
/**
30+
* Checks whether the given thread is a virtual thread.
31+
*
32+
* @param thread The thread to check.
33+
* @return {@code true} if the given thread is virtual, {@code false} otherwise.
34+
*/
35+
public static boolean isVirtual(Thread thread) {
36+
if (MH_IS_VIRTUAL == null) {
37+
return false;
38+
}
39+
try {
40+
return (boolean) MH_IS_VIRTUAL.invoke(thread);
41+
} catch (Throwable e) {
42+
return false;
43+
}
44+
}
45+
46+
public static Optional<ExecutorService> newVirtualThreadPerTaskExecutor() {
47+
if (MH_NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR == null) {
48+
return Optional.empty();
49+
} else {
50+
try {
51+
ExecutorService executorService =
52+
(ExecutorService) MH_NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR.invoke();
53+
return Optional.of(executorService);
54+
} catch (Throwable e) {
55+
return Optional.empty();
56+
}
57+
}
58+
}
59+
60+
private static MethodHandle findIsVirtualMethodHandle() {
61+
if (JavaVersion.getRuntimeVersion().isAtLeast(21, 0, 0)) {
62+
try {
63+
return MethodHandles.lookup()
64+
.findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class));
65+
} catch (Throwable ignored) {
66+
}
67+
}
68+
return null;
69+
}
70+
71+
private static MethodHandle findNewVirtualThreadPerTaskExecutorMethodHandle() {
72+
if (JavaVersion.getRuntimeVersion().isAtLeast(21, 0, 0)) {
73+
try {
74+
return MethodHandles.lookup()
75+
.findStatic(
76+
Executors.class,
77+
"newVirtualThreadPerTaskExecutor",
78+
MethodType.methodType(ExecutorService.class));
79+
} catch (Throwable ignored) {
80+
}
81+
}
82+
return null;
83+
}
84+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package datadog.environment;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.fail;
5+
import static org.junit.jupiter.api.condition.JRE.JAVA_21;
6+
7+
import java.util.concurrent.ExecutorService;
8+
import java.util.concurrent.Executors;
9+
import java.util.concurrent.Future;
10+
import org.junit.jupiter.api.AfterAll;
11+
import org.junit.jupiter.api.BeforeAll;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.condition.EnabledOnJre;
14+
15+
class ThreadSupportTest {
16+
private static ExecutorService singleThreadExecutor;
17+
private static ExecutorService newVirtualThreadPerTaskExecutor;
18+
19+
@BeforeAll
20+
static void beforeAll() {
21+
singleThreadExecutor = Executors.newSingleThreadExecutor();
22+
newVirtualThreadPerTaskExecutor = ThreadSupport.newVirtualThreadPerTaskExecutor().orElse(null);
23+
}
24+
25+
@Test
26+
void testPlatformThread() {
27+
assertVirtualThread(singleThreadExecutor, false);
28+
}
29+
30+
@Test
31+
@EnabledOnJre(JAVA_21)
32+
void testVirtualThread() {
33+
assertVirtualThread(newVirtualThreadPerTaskExecutor, true);
34+
}
35+
36+
static void assertVirtualThread(ExecutorService executorService, boolean expected) {
37+
Future<Boolean> futureCurrent = executorService.submit(() -> ThreadSupport.isVirtual());
38+
Future<Boolean> futureGiven =
39+
executorService.submit(
40+
() -> {
41+
Thread thread = Thread.currentThread();
42+
return ThreadSupport.isVirtual(thread);
43+
});
44+
try {
45+
assertEquals(expected, futureCurrent.get(), "invalid current thread virtual status");
46+
assertEquals(expected, futureGiven.get(), "invalid given thread virtual status");
47+
} catch (Throwable e) {
48+
fail("Can't get thread virtual status", e);
49+
}
50+
}
51+
52+
@AfterAll
53+
static void afterAll() {
54+
singleThreadExecutor.shutdown();
55+
if (newVirtualThreadPerTaskExecutor != null) {
56+
newVirtualThreadPerTaskExecutor.shutdown();
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)