-
Notifications
You must be signed in to change notification settings - Fork 315
Improve support for virtual thread detection #9852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
PerfectSlayer
wants to merge
1
commit into
master
Choose a base branch
from
bbujon/environment-virtual-threads
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
142 changes: 142 additions & 0 deletions
142
components/environment/src/main/java/datadog/environment/ThreadSupport.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
| * | ||
| * <p>Uses feature detection and provides static helpers to work with different versions of Java. | ||
| * | ||
| * <p>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<ExecutorService> newVirtualThreadPerTaskExecutor() { | ||
| if (NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR_MH != null) { | ||
| try { | ||
| ExecutorService executorService = | ||
| (ExecutorService) NEW_VIRTUAL_THREAD_PER_TASK_EXECUTOR_MH.invoke(); | ||
| return Optional.of(executorService); | ||
| } catch (Throwable ignored) { | ||
| } | ||
| } | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| private static MethodHandle findThreadIdMethodHandle() { | ||
| if (JavaVirtualMachine.isJavaVersionAtLeast(19)) { | ||
| try { | ||
| return MethodHandles.lookup() | ||
| .findVirtual(Thread.class, "threadId", MethodType.methodType(long.class)); | ||
| } catch (Throwable ignored) { | ||
| return null; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static MethodHandle findIsVirtualMethodHandle() { | ||
| if (JavaVirtualMachine.isJavaVersionAtLeast(21)) { | ||
| try { | ||
| return MethodHandles.lookup() | ||
| .findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class)); | ||
| } catch (Throwable ignored) { | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private static MethodHandle findNewVirtualThreadPerTaskExecutorMethodHandle() { | ||
| if (JavaVirtualMachine.isJavaVersionAtLeast(21)) { | ||
| try { | ||
| return MethodHandles.lookup() | ||
| .findStatic( | ||
| Executors.class, | ||
| "newVirtualThreadPerTaskExecutor", | ||
| MethodType.methodType(ExecutorService.class)); | ||
| } catch (Throwable ignored) { | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
85 changes: 0 additions & 85 deletions
85
components/environment/src/main/java/datadog/environment/ThreadUtils.java
This file was deleted.
Oops, something went wrong.
82 changes: 82 additions & 0 deletions
82
components/environment/src/test/java/datadog/environment/ThreadSupportTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Boolean> futureCurrent = executorService.submit(() -> ThreadSupport.isVirtual()); | ||
| Future<Boolean> futureGiven = | ||
| executorService.submit( | ||
| () -> { | ||
| Thread thread = Thread.currentThread(); | ||
| return ThreadSupport.isVirtual(thread); | ||
| }); | ||
| try { | ||
| assertEquals(expected, futureCurrent.get(), "invalid current thread virtual status"); | ||
| assertEquals(expected, futureGiven.get(), "invalid given thread virtual status"); | ||
| } catch (Throwable e) { | ||
| fail("Can't get thread virtual status", e); | ||
| } | ||
| } | ||
|
|
||
| @AfterAll | ||
| static void afterAll() { | ||
| singleThreadExecutor.shutdown(); | ||
| if (newVirtualThreadPerTaskExecutor != null) { | ||
| newVirtualThreadPerTaskExecutor.shutdown(); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have this utility class
datadog.trace.util.MethodHandlesfor dealing with method handles lookup that might be handy to use. It also logs in case of failure.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s interesting but it’s part of
:internal-apiso it won’t be accessible.Additionally, do we expect to log (in telemetry) when we are not sure about the method availability? Here, we are trying to get it to check if the capability is available but it’s fine if it isn’t.