Skip to content

Commit

Permalink
common: ThreadUtil: add support for "fake virtual threads"
Browse files Browse the repository at this point in the history
We may want to treat platform Threads as virtual ones, so we can test
our virtual thread logic on older JVMs, etc.

Add corresponding helper methods (which may track "treat as virtual"
state via a ThreadLocal, for example), add fallbacks for other cases,
and remove UnsupportedOperationException where now no longer applicable.
  • Loading branch information
kohlschuetter committed Jun 30, 2024
1 parent 4764f85 commit b33b2e3
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,41 @@
*/
@IgnoreJRERequirement // see src/main/java20
public final class ThreadUtil {
private static final ThreadLocal<Boolean> TREAT_AS_VIRTUAL_THREAD = new ThreadLocal<>();

private ThreadUtil() {
throw new IllegalStateException("No instances");
}

/**
* Checks if the current platform Thread is treated as a virtual one.
*
* @return {@code true} if so.
*/
private static boolean isTreatAsVirtualThread() {
return Boolean.TRUE.equals(TREAT_AS_VIRTUAL_THREAD.get());
}

/**
* Marks the current platform {@link Thread} to be treated as a virtual thread, if possible. Has
* no effect if the current Thread already is a virtual thread.
*
* @param b {@code true} to enable treatment of a platform thread as a virtual thread.
*/
public static void setTreatAsVirtualThread(boolean b) {
if (isVirtualThread()) {
return;
}
TREAT_AS_VIRTUAL_THREAD.set(b);
}

/**
* Checks if the current {@link Thread} is to be considered a virtual thread.
*
* @return {@code true} if so.
*/
public static boolean isVirtualThread() {
return Thread.currentThread().isVirtual();
return Thread.currentThread().isVirtual() || isTreatAsVirtualThread();
}

/**
Expand All @@ -57,10 +81,11 @@ public static boolean isVirtualThreadSupported() {
}

/**
* Returns a new "virtual thread per task executor".
* Returns a new "virtual thread per task executor". If virtual threads are not supported by this
* JVM, a new platform thread are created for each task, and such threads are marked to be treated
* as virtual threads.
*
* @return The new executor service.
* @throws UnsupportedOperationException if not possible
*/
public static ExecutorService newVirtualThreadPerTaskExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
Expand All @@ -83,11 +108,11 @@ public static boolean checkNotInterruptedOrThrow() throws InterruptedIOException
/**
* Starts a new daemon thread.
*
* @param virtual If {@code true}, start a virtual Thread instead of a platform Thread; if
* @param virtual If {@code true}, try to start a virtual Thread instead of a platform Thread (or
* at least pretend it's a virtual thread if they're not supported natively); if
* {@code false}, a "daemon" platform thread is started.
* @param run The runnable.
* @return The thread.
* @throws UnsupportedOperationException if not possible
*/
public static Thread startNewDaemonThread(boolean virtual, Runnable run) {
if (virtual) {
Expand Down Expand Up @@ -132,7 +157,17 @@ public static void runOnSystemThread(Runnable op) throws InterruptedException {
}
}
} else {
op.run();
boolean treatAsVirtual = isTreatAsVirtualThread();
if (treatAsVirtual) {
setTreatAsVirtualThread(false);
}
try {
op.run();
} finally {
if (treatAsVirtual) {
setTreatAsVirtualThread(treatAsVirtual);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
Expand All @@ -32,17 +36,41 @@
*/
@IgnoreJRERequirement // see src/main/java20
public final class ThreadUtil {
private static final ThreadLocal<Boolean> TREAT_AS_VIRTUAL_THREAD = new ThreadLocal<>();

private ThreadUtil() {
throw new IllegalStateException("No instances");
}

/**
* Checks if the current platform Thread is treated as a virtual one.
*
* @return {@code true} if so.
*/
private static boolean isTreatAsVirtualThread() {
return Boolean.TRUE.equals(TREAT_AS_VIRTUAL_THREAD.get());
}

/**
* Marks the current platform {@link Thread} to be treated as a virtual thread, if possible. Has
* no effect if the current Thread already is a virtual thread.
*
* @param b {@code true} to enable treatment of a platform thread as a virtual thread.
*/
public static void setTreatAsVirtualThread(boolean b) {
if (isVirtualThread()) {
return;
}
TREAT_AS_VIRTUAL_THREAD.set(b);
}

/**
* Checks if the current {@link Thread} is to be considered a virtual thread.
*
* @return {@code true} if so.
*/
public static boolean isVirtualThread() {
return false;
return isTreatAsVirtualThread();
}

/**
Expand All @@ -56,13 +84,15 @@ public static boolean isVirtualThreadSupported() {
}

/**
* Returns a new "virtual thread per task executor".
* Returns a new "virtual thread per task executor". If virtual threads are not supported by this
* JVM, a new platform thread are created for each task, and such threads are marked to be treated
* as virtual threads.
*
* @return The new executor service.
* @throws UnsupportedOperationException if not possible
*/
public static ExecutorService newVirtualThreadPerTaskExecutor() {
throw new UnsupportedOperationException("Virtual threads are not supported by this JVM");
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), (r) -> startNewDaemonThread(true, r));
}

/**
Expand All @@ -82,21 +112,24 @@ public static boolean checkNotInterruptedOrThrow() throws InterruptedIOException
/**
* Starts a new daemon thread.
*
* @param virtual If {@code true}, start a virtual Thread instead of a platform Thread; if
* @param virtual If {@code true}, try to start a virtual Thread instead of a platform Thread (or
* at least pretend it's a virtual thread if they're not supported natively); if
* {@code false}, a "daemon" platform thread is started.
* @param run The runnable.
* @return The thread.
* @throws UnsupportedOperationException if not possible
*/
public static Thread startNewDaemonThread(boolean virtual, Runnable run) {
if (virtual) {
throw new UnsupportedOperationException("Virtual threads are not supported by this JVM");
} else {
Thread t = new Thread(run);
t.setDaemon(true);
t.start();
return t;
final Runnable run0 = run;
run = () -> {
setTreatAsVirtualThread(true);
run0.run();
};
}
Thread t = new Thread(run);
t.setDaemon(true);
t.start();
return t;
}

/**
Expand All @@ -109,6 +142,16 @@ public static Thread startNewDaemonThread(boolean virtual, Runnable run) {
* @throws InterruptedException on interrupt.
*/
public static void runOnSystemThread(Runnable op) throws InterruptedException {
op.run();
boolean treatAsVirtual = isTreatAsVirtualThread();
if (treatAsVirtual) {
setTreatAsVirtualThread(false);
}
try {
op.run();
} finally {
if (treatAsVirtual) {
setTreatAsVirtualThread(treatAsVirtual);
}
}
}
}

0 comments on commit b33b2e3

Please sign in to comment.