8336254: Virtual thread implementation + test updates

Reviewed-by: sspitsyn, kevinw
This commit is contained in:
Alan Bateman 2024-07-25 04:59:01 +00:00
parent d3e51daf73
commit 6e228ce382
39 changed files with 2741 additions and 1363 deletions

View File

@ -871,6 +871,7 @@ ifeq ($(call isTargetOs, windows), true)
BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libMonitorWithDeadObjectTest.c libTestPsig.c exeGetCreatedJavaVMs.c
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libnativeStack := java.base:libjvm
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
else
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libsystemclssearch_agent += -lpthread
@ -1509,6 +1510,7 @@ else
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCompleteExit += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeGetCreatedJavaVMs := -lpthread
BUILD_HOTSPOT_JTREG_EXECUTABLES_JDK_LIBS_exeGetCreatedJavaVMs := java.base:libjvm

View File

@ -65,7 +65,6 @@ import java.util.PropertyPermission;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
@ -2264,6 +2263,7 @@ public final class System {
super(fd);
}
@Override
public void write(int b) throws IOException {
boolean attempted = Blocker.begin();
try {
@ -2677,14 +2677,6 @@ public final class System {
return Thread.currentCarrierThread();
}
public <V> V executeOnCarrierThread(Callable<V> task) throws Exception {
if (Thread.currentThread() instanceof VirtualThread vthread) {
return vthread.executeOnCarrierThread(task);
} else {
return task.call();
}
}
public <T> T getCarrierThreadLocal(CarrierThreadLocal<T> local) {
return ((ThreadLocal<T>)local).getCarrierThreadLocal();
}

View File

@ -40,6 +40,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import jdk.internal.event.VirtualThreadEndEvent;
import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.internal.event.VirtualThreadStartEvent;
@ -62,14 +63,13 @@ import sun.security.action.GetPropertyAction;
import static java.util.concurrent.TimeUnit.*;
/**
* A thread that is scheduled by the Java virtual machine rather than the operating
* system.
* A thread that is scheduled by the Java virtual machine rather than the operating system.
*/
final class VirtualThread extends BaseVirtualThread {
private static final Unsafe U = Unsafe.getUnsafe();
private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler();
private static final ScheduledExecutorService[] DELAYED_TASK_SCHEDULERS = createDelayedTaskSchedulers();
private static final int TRACE_PINNING_MODE = tracePinningMode();
private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
@ -217,7 +217,7 @@ final class VirtualThread extends BaseVirtualThread {
* on the current thread before the task runs or continues. It unmounts when the
* task completes or yields.
*/
@ChangesCurrentThread
@ChangesCurrentThread // allow mount/unmount to be inlined
private void runContinuation() {
// the carrier must be a platform thread
if (Thread.currentThread().isVirtual()) {
@ -257,42 +257,109 @@ final class VirtualThread extends BaseVirtualThread {
* Submits the runContinuation task to the scheduler. For the default scheduler,
* and calling it on a worker thread, the task will be pushed to the local queue,
* otherwise it will be pushed to an external submission queue.
* @param scheduler the scheduler
* @param retryOnOOME true to retry indefinitely if OutOfMemoryError is thrown
* @throws RejectedExecutionException
*/
private void submitRunContinuation() {
try {
scheduler.execute(runContinuation);
} catch (RejectedExecutionException ree) {
submitFailed(ree);
throw ree;
@ChangesCurrentThread
private void submitRunContinuation(Executor scheduler, boolean retryOnOOME) {
boolean done = false;
while (!done) {
try {
// The scheduler's execute method is invoked in the context of the
// carrier thread. For the default scheduler this ensures that the
// current thread is a ForkJoinWorkerThread so the task will be pushed
// to the local queue. For other schedulers, it avoids deadlock that
// would arise due to platform and virtual threads contending for a
// lock on the scheduler's submission queue.
if (currentThread() instanceof VirtualThread vthread) {
vthread.switchToCarrierThread();
try {
scheduler.execute(runContinuation);
} finally {
switchToVirtualThread(vthread);
}
} else {
scheduler.execute(runContinuation);
}
done = true;
} catch (RejectedExecutionException ree) {
submitFailed(ree);
throw ree;
} catch (OutOfMemoryError e) {
if (retryOnOOME) {
U.park(false, 100_000_000); // 100ms
} else {
throw e;
}
}
}
}
/**
* Submits the runContinuation task to given scheduler with a lazy submit.
* If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
* @throws RejectedExecutionException
* @see ForkJoinPool#lazySubmit(ForkJoinTask)
*/
private void lazySubmitRunContinuation(ForkJoinPool pool) {
assert Thread.currentThread() instanceof CarrierThread;
try {
pool.lazySubmit(ForkJoinTask.adapt(runContinuation));
} catch (RejectedExecutionException ree) {
submitFailed(ree);
throw ree;
} catch (OutOfMemoryError e) {
submitRunContinuation(pool, true);
}
}
/**
* Submits the runContinuation task to the given scheduler as an external submit.
* If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
* @throws RejectedExecutionException
* @see ForkJoinPool#externalSubmit(ForkJoinTask)
*/
private void externalSubmitRunContinuation(ForkJoinPool pool) {
assert Thread.currentThread() instanceof CarrierThread;
try {
pool.externalSubmit(ForkJoinTask.adapt(runContinuation));
} catch (RejectedExecutionException ree) {
submitFailed(ree);
throw ree;
} catch (OutOfMemoryError e) {
submitRunContinuation(pool, true);
}
}
/**
* Submits the runContinuation task to the scheduler. For the default scheduler,
* and calling it on a worker thread, the task will be pushed to the local queue,
* otherwise it will be pushed to an external submission queue.
* If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
* @throws RejectedExecutionException
*/
private void submitRunContinuation() {
submitRunContinuation(scheduler, true);
}
/**
* Submits the runContinuation task to the scheduler. For the default scheduler, and
* calling it a virtual thread that uses the default scheduler, the task will be
* pushed to an external submission queue. This method may throw OutOfMemoryError.
* @throws RejectedExecutionException
* @throws OutOfMemoryError
*/
private void externalSubmitRunContinuationOrThrow() {
if (scheduler == DEFAULT_SCHEDULER && currentCarrierThread() instanceof CarrierThread ct) {
try {
ct.getPool().externalSubmit(ForkJoinTask.adapt(runContinuation));
} catch (RejectedExecutionException ree) {
submitFailed(ree);
throw ree;
}
} else {
submitRunContinuation(scheduler, false);
}
}
@ -385,6 +452,8 @@ final class VirtualThread extends BaseVirtualThread {
@ChangesCurrentThread
@ReservedStackAccess
private void unmount() {
assert !Thread.holdsLock(interruptLock);
// set Thread.currentThread() to return the platform thread
Thread carrier = this.carrierThread;
carrier.setCurrentThread(carrier);
@ -417,7 +486,7 @@ final class VirtualThread extends BaseVirtualThread {
*/
@ChangesCurrentThread
@JvmtiMountTransition
private void switchToVirtualThread(VirtualThread vthread) {
private static void switchToVirtualThread(VirtualThread vthread) {
Thread carrier = vthread.carrierThread;
assert carrier == Thread.currentCarrierThread();
carrier.setCurrentThread(vthread);
@ -474,13 +543,12 @@ final class VirtualThread extends BaseVirtualThread {
// may have been unparked while parking
if (parkPermit && compareAndSetState(newState, UNPARKED)) {
// lazy submit to continue on the current thread as carrier if possible
if (currentThread() instanceof CarrierThread ct) {
// lazy submit to continue on the current carrier if possible
if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
lazySubmitRunContinuation(ct.getPool());
} else {
submitRunContinuation();
}
}
return;
}
@ -561,8 +629,8 @@ final class VirtualThread extends BaseVirtualThread {
// scoped values may be inherited
inheritScopedValueBindings(container);
// submit task to run thread
submitRunContinuation();
// submit task to run thread, using externalSubmit if possible
externalSubmitRunContinuationOrThrow();
started = true;
} finally {
if (!started) {
@ -707,7 +775,7 @@ final class VirtualThread extends BaseVirtualThread {
// need to switch to current carrier thread to avoid nested parking
switchToCarrierThread();
try {
return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS);
return schedule(this::unpark, nanos, NANOSECONDS);
} finally {
switchToVirtualThread(this);
}
@ -718,6 +786,7 @@ final class VirtualThread extends BaseVirtualThread {
*/
@ChangesCurrentThread
private void cancel(Future<?> future) {
assert Thread.currentThread() == this;
if (!future.isDone()) {
// need to switch to current carrier thread to avoid nested parking
switchToCarrierThread();
@ -730,33 +799,26 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Re-enables this virtual thread for scheduling. If the virtual thread was
* {@link #park() parked} then it will be unblocked, otherwise its next call
* to {@code park} or {@linkplain #parkNanos(long) parkNanos} is guaranteed
* not to block.
* Re-enables this virtual thread for scheduling. If this virtual thread is parked
* then its task is scheduled to continue, otherwise its next call to {@code park} or
* {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block.
* @throws RejectedExecutionException if the scheduler cannot accept a task
*/
@Override
@ChangesCurrentThread
void unpark() {
Thread currentThread = Thread.currentThread();
if (!getAndSetParkPermit(true) && currentThread != this) {
if (!getAndSetParkPermit(true) && currentThread() != this) {
int s = state();
boolean parked = (s == PARKED) || (s == TIMED_PARKED);
if (parked && compareAndSetState(s, UNPARKED)) {
if (currentThread instanceof VirtualThread vthread) {
vthread.switchToCarrierThread();
try {
submitRunContinuation();
} finally {
switchToVirtualThread(vthread);
}
} else {
submitRunContinuation();
}
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
// unparked while parked
if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) {
submitRunContinuation();
return;
}
// unparked while parked when pinned
if (s == PINNED || s == TIMED_PINNED) {
// unpark carrier thread when pinned
notifyJvmtiDisableSuspend(true);
disableSuspendAndPreempt();
try {
synchronized (carrierThreadAccessLock()) {
Thread carrier = carrierThread;
@ -765,8 +827,9 @@ final class VirtualThread extends BaseVirtualThread {
}
}
} finally {
notifyJvmtiDisableSuspend(false);
enableSuspendAndPreempt();
}
return;
}
}
}
@ -859,11 +922,11 @@ final class VirtualThread extends BaseVirtualThread {
@Override
void blockedOn(Interruptible b) {
notifyJvmtiDisableSuspend(true);
disableSuspendAndPreempt();
try {
super.blockedOn(b);
} finally {
notifyJvmtiDisableSuspend(false);
enableSuspendAndPreempt();
}
}
@ -874,9 +937,9 @@ final class VirtualThread extends BaseVirtualThread {
checkAccess();
// if current thread is a virtual thread then prevent it from being
// suspended when entering or holding interruptLock
// suspended or unmounted when entering or holding interruptLock
Interruptible blocker;
notifyJvmtiDisableSuspend(true);
disableSuspendAndPreempt();
try {
synchronized (interruptLock) {
interrupted = true;
@ -890,18 +953,22 @@ final class VirtualThread extends BaseVirtualThread {
if (carrier != null) carrier.setInterrupt();
}
} finally {
notifyJvmtiDisableSuspend(false);
enableSuspendAndPreempt();
}
// notify blocker after releasing interruptLock
if (blocker != null) {
blocker.postInterrupt();
}
// make available parking permit, unpark thread if parked
unpark();
} else {
interrupted = true;
carrierThread.setInterrupt();
setParkPermit(true);
}
unpark();
}
@Override
@ -914,14 +981,14 @@ final class VirtualThread extends BaseVirtualThread {
assert Thread.currentThread() == this;
boolean oldValue = interrupted;
if (oldValue) {
notifyJvmtiDisableSuspend(true);
disableSuspendAndPreempt();
try {
synchronized (interruptLock) {
interrupted = false;
carrierThread.clearInterrupt();
}
} finally {
notifyJvmtiDisableSuspend(false);
enableSuspendAndPreempt();
}
}
return oldValue;
@ -946,16 +1013,18 @@ final class VirtualThread extends BaseVirtualThread {
return Thread.State.RUNNABLE;
case RUNNING:
// if mounted then return state of carrier thread
notifyJvmtiDisableSuspend(true);
try {
synchronized (carrierThreadAccessLock()) {
Thread carrierThread = this.carrierThread;
if (carrierThread != null) {
return carrierThread.threadState();
if (Thread.currentThread() != this) {
disableSuspendAndPreempt();
try {
synchronized (carrierThreadAccessLock()) {
Thread carrierThread = this.carrierThread;
if (carrierThread != null) {
return carrierThread.threadState();
}
}
} finally {
enableSuspendAndPreempt();
}
} finally {
notifyJvmtiDisableSuspend(false);
}
// runnable, mounted
return Thread.State.RUNNABLE;
@ -1068,32 +1137,49 @@ final class VirtualThread extends BaseVirtualThread {
sb.append(name);
}
sb.append("]/");
Thread carrier = carrierThread;
if (carrier != null) {
// include the carrier thread state and name when mounted
notifyJvmtiDisableSuspend(true);
// add the carrier state and thread name when mounted
boolean mounted;
if (Thread.currentThread() == this) {
mounted = appendCarrierInfo(sb);
} else {
disableSuspendAndPreempt();
try {
synchronized (carrierThreadAccessLock()) {
carrier = carrierThread;
if (carrier != null) {
String stateAsString = carrier.threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT));
sb.append('@');
sb.append(carrier.getName());
}
mounted = appendCarrierInfo(sb);
}
} finally {
notifyJvmtiDisableSuspend(false);
enableSuspendAndPreempt();
}
}
// include virtual thread state when not mounted
if (carrier == null) {
// add virtual thread state when not mounted
if (!mounted) {
String stateAsString = threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT));
}
return sb.toString();
}
/**
* Appends the carrier state and thread name to the string buffer if mounted.
* @return true if mounted, false if not mounted
*/
private boolean appendCarrierInfo(StringBuilder sb) {
assert Thread.currentThread() == this || Thread.holdsLock(carrierThreadAccessLock());
Thread carrier = carrierThread;
if (carrier != null) {
String stateAsString = carrier.threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT));
sb.append('@');
sb.append(carrier.getName());
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
return (int) threadId();
@ -1127,6 +1213,22 @@ final class VirtualThread extends BaseVirtualThread {
return interruptLock;
}
/**
* Disallow the current thread be suspended or preempted.
*/
private void disableSuspendAndPreempt() {
notifyJvmtiDisableSuspend(true);
Continuation.pin();
}
/**
* Allow the current thread be suspended or preempted.
*/
private void enableSuspendAndPreempt() {
Continuation.unpin();
notifyJvmtiDisableSuspend(false);
}
// -- wrappers for get/set of state, parking permit, and carrier thread --
private int state() {
@ -1188,10 +1290,16 @@ final class VirtualThread extends BaseVirtualThread {
private static native void registerNatives();
static {
registerNatives();
// ensure VTHREAD_GROUP is created, may be accessed by JVMTI
var group = Thread.virtualThreadGroup();
// ensure VirtualThreadPinnedEvent is loaded/initialized
U.ensureClassInitialized(VirtualThreadPinnedEvent.class);
}
/**
* Creates the default scheduler.
* Creates the default ForkJoinPool scheduler.
*/
@SuppressWarnings("removal")
private static ForkJoinPool createDefaultScheduler() {
@ -1229,22 +1337,42 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Creates the ScheduledThreadPoolExecutor used for timed unpark.
* Schedule a runnable task to run after a delay.
*/
private static ScheduledExecutorService createDelayedTaskScheduler() {
String propValue = GetPropertyAction.privilegedGetProperty("jdk.unparker.maxPoolSize");
int poolSize;
private static Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
long tid = Thread.currentThread().threadId();
int index = (int) tid & (DELAYED_TASK_SCHEDULERS.length - 1);
return DELAYED_TASK_SCHEDULERS[index].schedule(command, delay, unit);
}
/**
* Creates the ScheduledThreadPoolExecutors used to execute delayed tasks.
*/
private static ScheduledExecutorService[] createDelayedTaskSchedulers() {
String propName = "jdk.virtualThreadScheduler.timerQueues";
String propValue = GetPropertyAction.privilegedGetProperty(propName);
int queueCount;
if (propValue != null) {
poolSize = Integer.parseInt(propValue);
queueCount = Integer.parseInt(propValue);
if (queueCount != Integer.highestOneBit(queueCount)) {
throw new RuntimeException("Value of " + propName + " must be power of 2");
}
} else {
poolSize = 1;
int ncpus = Runtime.getRuntime().availableProcessors();
queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1);
}
ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor)
Executors.newScheduledThreadPool(poolSize, task -> {
return InnocuousThread.newThread("VirtualThread-unparker", task);
});
stpe.setRemoveOnCancelPolicy(true);
return stpe;
var schedulers = new ScheduledExecutorService[queueCount];
for (int i = 0; i < queueCount; i++) {
ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor)
Executors.newScheduledThreadPool(1, task -> {
Thread t = InnocuousThread.newThread("VirtualThread-unparker", task);
t.setDaemon(true);
return t;
});
stpe.setRemoveOnCancelPolicy(true);
schedulers[i] = stpe;
}
return schedulers;
}
/**

View File

@ -35,7 +35,9 @@
package java.util.concurrent.locks;
import jdk.internal.misc.VirtualThreads;
import java.util.concurrent.TimeUnit;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.Unsafe;
/**
@ -176,7 +178,7 @@ public class LockSupport {
public static void unpark(Thread thread) {
if (thread != null) {
if (thread.isVirtual()) {
VirtualThreads.unpark(thread);
JLA.unparkVirtualThread(thread);
} else {
U.unpark(thread);
}
@ -216,7 +218,7 @@ public class LockSupport {
setBlocker(t, blocker);
try {
if (t.isVirtual()) {
VirtualThreads.park();
JLA.parkVirtualThread();
} else {
U.park(false, 0L);
}
@ -264,7 +266,7 @@ public class LockSupport {
setBlocker(t, blocker);
try {
if (t.isVirtual()) {
VirtualThreads.park(nanos);
JLA.parkVirtualThread(nanos);
} else {
U.park(false, nanos);
}
@ -311,11 +313,7 @@ public class LockSupport {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
try {
if (t.isVirtual()) {
VirtualThreads.parkUntil(deadline);
} else {
U.park(true, deadline);
}
parkUntil(deadline);
} finally {
setBlocker(t, null);
}
@ -366,7 +364,7 @@ public class LockSupport {
*/
public static void park() {
if (Thread.currentThread().isVirtual()) {
VirtualThreads.park();
JLA.parkVirtualThread();
} else {
U.park(false, 0L);
}
@ -405,7 +403,7 @@ public class LockSupport {
public static void parkNanos(long nanos) {
if (nanos > 0) {
if (Thread.currentThread().isVirtual()) {
VirtualThreads.park(nanos);
JLA.parkVirtualThread(nanos);
} else {
U.park(false, nanos);
}
@ -444,7 +442,8 @@ public class LockSupport {
*/
public static void parkUntil(long deadline) {
if (Thread.currentThread().isVirtual()) {
VirtualThreads.parkUntil(deadline);
long millis = deadline - System.currentTimeMillis();
JLA.parkVirtualThread(TimeUnit.MILLISECONDS.toNanos(millis));
} else {
U.park(true, deadline);
}
@ -462,4 +461,5 @@ public class LockSupport {
private static final long PARKBLOCKER
= U.objectFieldOffset(Thread.class, "parkBlocker");
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
}

View File

@ -42,7 +42,6 @@ import java.security.ProtectionDomain;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Stream;
@ -504,11 +503,6 @@ public interface JavaLangAccess {
*/
Thread currentCarrierThread();
/**
* Executes the given value returning task on the current carrier thread.
*/
<V> V executeOnCarrierThread(Callable<V> task) throws Exception;
/**
* Returns the value of the current carrier thread's copy of a thread-local.
*/

View File

@ -1,95 +0,0 @@
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.misc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.RejectedExecutionException;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
/**
* Defines static methods to support execution in the context of a virtual thread.
*/
public final class VirtualThreads {
private static final JavaLangAccess JLA;
static {
JLA = SharedSecrets.getJavaLangAccess();
if (JLA == null) {
throw new InternalError("JavaLangAccess not setup");
}
}
private VirtualThreads() { }
/**
* Parks the current virtual thread until it is unparked or interrupted.
* If already unparked then the parking permit is consumed and this method
* completes immediately (meaning it doesn't yield). It also completes
* immediately if the interrupt status is set.
* @throws WrongThreadException if the current thread is not a virtual thread
*/
public static void park() {
JLA.parkVirtualThread();
}
/**
* Parks the current virtual thread up to the given waiting time or until it
* is unparked or interrupted. If already unparked then the parking permit is
* consumed and this method completes immediately (meaning it doesn't yield).
* It also completes immediately if the interrupt status is set or the waiting
* time is {@code <= 0}.
* @param nanos the maximum number of nanoseconds to wait
* @throws WrongThreadException if the current thread is not a virtual thread
*/
public static void park(long nanos) {
JLA.parkVirtualThread(nanos);
}
/**
* Parks the current virtual thread until the given deadline or until is is
* unparked or interrupted. If already unparked then the parking permit is
* consumed and this method completes immediately (meaning it doesn't yield).
* It also completes immediately if the interrupt status is set or the
* deadline has past.
* @param deadline absolute time, in milliseconds, from the epoch
* @throws WrongThreadException if the current thread is not a virtual thread
*/
public static void parkUntil(long deadline) {
long millis = deadline - System.currentTimeMillis();
long nanos = TimeUnit.NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS);
park(nanos);
}
/**
* Re-enables a virtual thread for scheduling. If the thread was parked then
* it will be unblocked, otherwise its next attempt to park will not block
* @param thread the virtual thread to unpark
* @throws IllegalArgumentException if the thread is not a virtual thread
* @throws RejectedExecutionException if the scheduler cannot accept a task
*/
public static void unpark(Thread thread) {
JLA.unparkVirtualThread(thread);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -79,7 +79,7 @@ public class framecnt01 {
}
// this is too fragile, implementation can change at any time.
checkFrames(vThread1, false, 14);
checkFrames(vThread1, false, 13);
LockSupport.unpark(vThread1);
vThread1.join();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,24 +25,29 @@
* @test id=default
* @bug 8312498
* @summary Basic test for JVMTI GetThreadState with virtual threads
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit/othervm/native GetThreadStateTest
* @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest
*/
/*
* @test id=no-vmcontinuations
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations --enable-native-access=ALL-UNNAMED GetThreadStateTest
*/
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class GetThreadStateTest {
@ -51,6 +56,11 @@ class GetThreadStateTest {
static void setup() {
System.loadLibrary("GetThreadStateTest");
init();
// need >=2 carriers for testing pinning when main thread is a virtual thread
if (Thread.currentThread().isVirtual()) {
VThreadRunner.ensureParallelism(2);
}
}
/**
@ -105,21 +115,29 @@ class GetThreadStateTest {
}
/**
* Test state of thread waiting to enter a monitor.
* Test state of thread waiting to enter a monitor when pinned and not pinned.
*/
@Test
void testMonitorEnter() throws Exception {
var started = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testMonitorEnter(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
Object lock = new Object();
var thread = Thread.ofVirtual().unstarted(() -> {
started.set(true);
synchronized (lock) { }
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
synchronized (lock) { }
});
} else {
ready.set(true);
synchronized (lock) { }
}
});
try {
synchronized (lock) {
// start thread and wait for it to start execution
thread.start();
awaitTrue(started);
awaitTrue(ready);
// thread should block on monitor enter
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
@ -135,23 +153,31 @@ class GetThreadStateTest {
}
/**
* Test state of thread waiting in Object.wait().
* Test state of thread waiting in Object.wait() when pinned and not pinned.
*/
@Test
void testObjectWait() throws Exception {
var started = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testObjectWait(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
started.set(true);
try {
lock.wait();
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait();
});
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to start execution
awaitTrue(started);
awaitTrue(ready);
// thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE |
@ -177,23 +203,33 @@ class GetThreadStateTest {
}
/**
* Test state of thread waiting in Object.wait(millis).
* Test state of thread waiting in Object.wait(millis) when pinned and not pinned.
*/
@Test
void testObjectWaitMillis() throws Exception {
var started = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testObjectWaitMillis(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
started.set(true);
try {
lock.wait(Long.MAX_VALUE);
} catch (InterruptedException e) { }
synchronized (lock) {
try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(Long.MAX_VALUE);
});
} else {
ready.set(true);
lock.wait(Long.MAX_VALUE);
}
} catch (InterruptedException e) { }
}
}
});
try {
// wait for thread to start execution
awaitTrue(started);
awaitTrue(ready);
// thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE |
@ -219,83 +255,31 @@ class GetThreadStateTest {
}
/**
* Test state of thread parked with LockSupport.park.
* Test state of thread parked with LockSupport.park when pinned and not pinned.
*/
@Test
void testPark() throws Exception {
var started = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testPark(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
started.set(true);
while (!done.get()) {
LockSupport.park();
}
});
try {
// wait for thread to start execution
awaitTrue(started);
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_INDEFINITELY |
JVMTI_THREAD_STATE_PARKED;
await(thread, expected);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test state of thread parked with LockSupport.parkNanos.
*/
@Test
void testParkNanos() throws Exception {
var started = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
started.set(true);
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
try {
// wait for thread to start execution
awaitTrue(started);
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
JVMTI_THREAD_STATE_WAITING |
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
JVMTI_THREAD_STATE_PARKED;
await(thread, expected);
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test state of thread parked with LockSupport.park while holding a monitor.
*/
@Test
void testParkWhenPinned() throws Exception {
var started = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
started.set(true);
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
while (!done.get()) {
LockSupport.park();
}
});
} else {
ready.set(true);
while (!done.get()) {
LockSupport.park();
}
});
}
});
try {
// wait for thread to start execution
awaitTrue(started);
awaitTrue(ready);
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |
@ -311,23 +295,31 @@ class GetThreadStateTest {
}
/**
* Test state of thread parked with LockSupport.parkNanos while holding a monitor.
* Test state of thread parked with LockSupport.parkNanos when pinned and not pinned.
*/
@Test
void testParkNanosWhenPinned() throws Exception {
var started = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testParkNanos(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
started.set(true);
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
} else {
ready.set(true);
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
}
});
try {
// wait for thread to start execution
awaitTrue(started);
awaitTrue(ready);
// thread should park
int expected = JVMTI_THREAD_STATE_ALIVE |

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,47 +28,22 @@
* @requires vm.continuations
* @requires vm.jvmti
* @requires vm.compMode != "Xcomp"
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm/native
* -Djdk.virtualThreadScheduler.parallelism=9
* -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach
*/
import com.sun.tools.attach.VirtualMachine;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import java.util.List;
import java.util.ArrayList;
/*
* The test uses custom implementation of the CountDownLatch class.
* The reason is we want the state of tested thread to be predictable.
* With java.util.concurrent.CountDownLatch it is not clear what thread state is expected.
*/
class CountDownLatch {
private int count = 0;
CountDownLatch(int count) {
this.count = count;
}
public synchronized void countDown() {
count--;
notify();
}
public synchronized void await() throws InterruptedException {
while (count > 0) {
wait(1);
}
}
}
import jdk.test.lib.thread.VThreadRunner;
public class VThreadEventTest {
static final int TCNT1 = 10;
static final int TCNT2 = 4;
static final int TCNT3 = 4;
static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3;
static final int PARKED_THREAD_COUNT = 4;
static final int SPINNING_THREAD_COUNT = 4;
private static void log(String msg) { System.out.println(msg); }
@ -77,128 +52,96 @@ public class VThreadEventTest {
private static native int threadUnmountCount();
private static volatile boolean attached;
private static boolean failed;
private static List<Thread> test1Threads = new ArrayList(TCNT1);
private static CountDownLatch ready0 = new CountDownLatch(THREAD_CNT);
private static CountDownLatch ready1 = new CountDownLatch(TCNT1);
private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT);
private static CountDownLatch mready = new CountDownLatch(1);
private static void await(CountDownLatch dumpedLatch) {
try {
dumpedLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// called by agent when it is initialized and has enabled events
static void agentStarted() {
attached = true;
}
// The test1 vthreads are kept unmounted until interrupted after agent attach.
static final Runnable test1 = () -> {
synchronized (test1Threads) {
test1Threads.add(Thread.currentThread());
}
log("test1 vthread started");
ready0.countDown();
await(mready);
ready1.countDown(); // to guaranty state is not State.WAITING after await(mready)
try {
Thread.sleep(20000); // big timeout to keep unmounted until interrupted
} catch (InterruptedException ex) {
// it is expected, ignore
}
ready2.countDown();
};
// The test2 vthreads are kept mounted until agent attach.
static final Runnable test2 = () -> {
log("test2 vthread started");
ready0.countDown();
await(mready);
while (!attached) {
// keep mounted
}
ready2.countDown();
};
// The test3 vthreads are kept mounted until agent attach.
static final Runnable test3 = () -> {
log("test3 vthread started");
ready0.countDown();
await(mready);
while (!attached) {
// keep mounted
}
LockSupport.parkNanos(10_000_000L); // will cause extra mount and unmount
ready2.countDown();
};
public static void main(String[] args) throws Exception {
if (Runtime.getRuntime().availableProcessors() < 8) {
log("WARNING: test expects at least 8 processors.");
if (Thread.currentThread().isVirtual()) {
System.out.println("Skipping test as current thread is a virtual thread");
return;
}
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < TCNT1; i++) {
executorService.execute(test1);
VThreadRunner.ensureParallelism(SPINNING_THREAD_COUNT+1);
// start threads that park (unmount)
var threads1 = new ArrayList<Thread>();
for (int i = 0; i < PARKED_THREAD_COUNT; i++) {
var started = new AtomicBoolean();
var thread = Thread.startVirtualThread(() -> {
started.set(true);
LockSupport.park();
});
// wait for thread to start execution + park
while (!started.get()) {
Thread.sleep(10);
}
for (int i = 0; i < TCNT2; i++) {
executorService.execute(test2);
}
for (int i = 0; i < TCNT3; i++) {
executorService.execute(test3);
}
await(ready0);
mready.countDown();
await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1()
// wait for test1 threads to reach TIMED_WAITING state in sleep()
for (Thread t : test1Threads) {
Thread.State state = t.getState();
log("DBG: state: " + state);
while (state != Thread.State.TIMED_WAITING) {
Thread.sleep(10);
state = t.getState();
log("DBG: state: " + state);
await(thread, Thread.State.WAITING);
threads1.add(thread);
}
// start threads that spin (stay mounted)
var threads2 = new ArrayList<Thread>();
for (int i = 0; i < SPINNING_THREAD_COUNT; i++) {
var started = new AtomicBoolean();
var thread = Thread.startVirtualThread(() -> {
started.set(true);
while (!attached) {
Thread.onSpinWait();
}
}
});
VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
vm.loadAgentLibrary("VThreadEventTest");
Thread.sleep(200); // to allow the agent to get ready
// wait for thread to start execution
while (!started.get()) {
Thread.sleep(10);
}
threads2.add(thread);
}
attached = true;
for (Thread t : test1Threads) {
t.interrupt();
}
ready2.await();
// attach to the current VM
VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
vm.loadAgentLibrary("VThreadEventTest");
// wait for agent to start
while (!attached) {
Thread.sleep(10);
}
// wait until all VirtualThreadEnd events have been sent
for (int sleepNo = 1; threadEndCount() < THREAD_CNT; sleepNo++) {
Thread.sleep(100);
if (sleepNo % 100 == 0) { // 10 sec period of waiting
log("main: waited seconds: " + sleepNo/10);
}
// unpark the threads that were parked
for (Thread thread : threads1) {
LockSupport.unpark(thread);
}
// wait for all threads to terminate
for (Thread thread : threads1) {
thread.join();
}
for (Thread thread : threads2) {
thread.join();
}
int threadEndCnt = threadEndCount();
int threadMountCnt = threadMountCount();
int threadUnmountCnt = threadUnmountCount();
int threadEndExp = THREAD_CNT;
int threadMountExp = THREAD_CNT - TCNT2;
int threadUnmountExp = THREAD_CNT + TCNT3;
log("ThreadEnd cnt: " + threadEndCnt + " (expected: " + threadEndExp + ")");
log("ThreadMount cnt: " + threadMountCnt + " (expected: " + threadMountExp + ")");
log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")");
int threadCount = PARKED_THREAD_COUNT + SPINNING_THREAD_COUNT;
log("VirtualThreadEnd events: " + threadEndCnt + ", expected: " + threadCount);
log("VirtualThreadMount events: " + threadMountCnt + ", expected: " + PARKED_THREAD_COUNT);
log("VirtualThreadUnmount events: " + threadUnmountCnt + ", expected: " + threadCount);
if (threadEndCnt != threadEndExp) {
log("FAILED: unexpected count of ThreadEnd events");
boolean failed = false;
if (threadEndCnt != threadCount) {
log("FAILED: unexpected count of VirtualThreadEnd events");
failed = true;
}
if (threadMountCnt != threadMountExp) {
log("FAILED: unexpected count of ThreadMount events");
if (threadMountCnt != PARKED_THREAD_COUNT) {
log("FAILED: unexpected count of VirtualThreadMount events");
failed = true;
}
if (threadUnmountCnt != threadUnmountExp) {
log("FAILED: unexpected count of ThreadUnmount events");
if (threadUnmountCnt != threadCount) {
log("FAILED: unexpected count of VirtualThreadUnmount events");
failed = true;
}
if (failed) {
@ -206,5 +149,14 @@ public class VThreadEventTest {
}
}
private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
Thread.State state = thread.getState();
while (state != expectedState) {
assert state != Thread.State.TERMINATED : "Thread has terminated";
Thread.sleep(10);
state = thread.getState();
}
}
}

View File

@ -65,6 +65,11 @@ Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
jvmtiEventCallbacks callbacks;
jvmtiCapabilities caps;
jvmtiError err;
JNIEnv *env;
jsize nVMs;
jint res;
jclass clazz;
jmethodID mid;
LOG("Agent_OnAttach started\n");
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
@ -97,6 +102,41 @@ Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadUnmount");
LOG("vthread events enabled\n");
// call VThreadEventTest.agentStarted to notify test that agent has started
res = JNI_GetCreatedJavaVMs(&vm, 1, &nVMs);
if (res != JNI_OK) {
LOG("JNI_GetCreatedJavaVMs failed: %d\n", res);
return JNI_ERR;
}
res = vm->GetEnv((void **) &env, JNI_VERSION_21);
if (res != JNI_OK) {
LOG("GetEnv failed: %d\n", res);
return JNI_ERR;
}
clazz = env->FindClass("VThreadEventTest");
if (clazz == NULL) {
LOG("FindClass failed\n");
return JNI_ERR;
}
mid = env->GetStaticMethodID(clazz, "agentStarted", "()V");
if (mid == NULL) {
LOG("GetStaticMethodID failed\n");
return JNI_ERR;
}
env->CallStaticVoidMethod(clazz, mid);
if (env->ExceptionOccurred()) {
LOG("CallStaticVoidMethod failed\n");
return JNI_ERR;
}
LOG("Agent_OnAttach done\n");
return JVMTI_ERROR_NONE;
}

View File

@ -0,0 +1,243 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test id=default
* @bug 8284161 8286788
* @summary Test java.lang.management.ThreadInfo contains expected information for carrier threads
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit CarrierThreadInfo
*/
/**
* @test id=LM_LIGHTWEIGHT
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit/othervm -XX:LockingMode=2 CarrierThreadInfo
*/
/**
* @test id=LM_LEGACY
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit/othervm -XX:LockingMode=1 CarrierThreadInfo
*/
/**
* @test id=LM_MONITOR
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit/othervm -XX:LockingMode=0 CarrierThreadInfo
*/
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CarrierThreadInfo {
/**
* Test that ThreadInfo.getLockedMonitors returns information about a lock held by
* a carrier thread.
*/
@Test
void testCarrierThreadHoldsLock() throws Exception {
Object lock = new Object();
ThreadFactory factory = task -> Thread.ofPlatform().unstarted(() -> {
synchronized (lock) {
task.run();
}
});
try (var scheduler = new CustomScheduler(factory)) {
var started = new AtomicBoolean();
var done = new AtomicBoolean();
Thread vthread = scheduler.forkVirtualThread(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
awaitTrue(started);
// carrier threads holds the lock
long carrierId = scheduler.carrier().threadId();
ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
.getThreadInfo(new long[] { carrierId }, true, true)[0];
boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
.anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
assertTrue(holdsLock, "Carrier should hold lock");
} finally {
done.set(true);
}
}
}
/**
* Test that ThreadInfo.getLockedMonitors does not return information about a lock
* held by mounted virtual thread.
*/
@Test
void testVirtualThreadHoldsLock() throws Exception {
ThreadFactory factory = Executors.defaultThreadFactory();
try (var scheduler = new CustomScheduler(factory)) {
var started = new AtomicBoolean();
var lock = new Object();
var done = new AtomicBoolean();
Thread vthread = scheduler.forkVirtualThread(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
awaitTrue(started);
// carrier threads does not hold lock
long carrierId = scheduler.carrier().threadId();
ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
.getThreadInfo(new long[] { carrierId }, true, true)[0];
boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
.anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
assertFalse(holdsLock, "Carrier should not hold lock");
} finally {
done.set(true);
}
}
}
/**
* Test that ThreadInfo.getLockOwnerId and getLockInfo return information about a
* synthetic lock that make it appear that the carrier is blocking waiting on the
* virtual thread.
*/
@Test
void testCarrierThreadWaits() throws Exception {
ThreadFactory factory = Executors.defaultThreadFactory();
try (var scheduler = new CustomScheduler(factory)) {
var started = new AtomicBoolean();
var done = new AtomicBoolean();
Thread vthread = scheduler.forkVirtualThread(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
awaitTrue(started);
long carrierId = scheduler.carrier().threadId();
long vthreadId = vthread.threadId();
ThreadInfo threadInfo = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId);
assertNotNull(threadInfo);
// carrier should be blocked waiting for lock owned by virtual thread
assertEquals(vthreadId, threadInfo.getLockOwnerId());
// carrier thread should be on blocked waiting on virtual thread
LockInfo lockInfo = threadInfo.getLockInfo();
assertNotNull(lockInfo);
assertEquals(vthread.getClass().getName(), lockInfo.getClassName());
assertEquals(System.identityHashCode(vthread), lockInfo.getIdentityHashCode());
} finally {
done.set(true);
}
}
}
/**
* Custom scheduler with a single carrier thread.
*/
private static class CustomScheduler implements AutoCloseable {
private final ExecutorService pool;
private final Executor scheduler;
private final AtomicReference<Thread> carrierRef = new AtomicReference<>();
CustomScheduler(ThreadFactory factory) {
pool = Executors.newSingleThreadExecutor(factory);
scheduler = task -> {
pool.submit(() -> {
carrierRef.set(Thread.currentThread());
try {
task.run();
} finally {
carrierRef.set(null);
}
});
};
}
/**
* Returns the carrier thread if a virtual thread is mounted.
*/
Thread carrier() throws InterruptedException {
return carrierRef.get();
}
/**
* Starts a virtual thread to execute the give task.
*/
Thread forkVirtualThread(Runnable task) {
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread thread = factory.newThread(task);
thread.start();
return thread;
}
@Override
public void close() {
pool.close();
}
}
/**
* Waits for the boolean value to become true.
*/
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
while (!ref.get()) {
Thread.sleep(20);
}
}
}

View File

@ -1,110 +0,0 @@
/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8284161 8286788
* @summary Test that a carrier thread waits on a virtual thread
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @run junit CarrierThreadWaits
*/
/**
* @test
* @requires vm.continuations & vm.debug
* @modules java.base/java.lang:+open
* @run junit/othervm -XX:LockingMode=0 CarrierThreadWaits
*/
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CarrierThreadWaits {
@Test
void testCarrierThreadWaiting() throws Exception {
try (ForkJoinPool pool = new ForkJoinPool(1)) {
var carrierRef = new AtomicReference<Thread>();
var vthreadRef = new AtomicReference<Thread>();
Executor scheduler = task -> {
pool.submit(() -> {
Thread carrier = Thread.currentThread();
carrierRef.set(carrier);
Thread vthread = vthreadRef.get();
System.err.format("%s run task (%s) ...%n", carrier, vthread);
task.run();
System.err.format("%s task done (%s)%n", carrier, vthread);
});
};
// start a virtual thread that spins and remains mounted until "done"
var started = new AtomicBoolean();
var done = new AtomicBoolean();
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread vthread = builder.unstarted(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
vthreadRef.set(vthread);
vthread.start();
try {
// wait for virtual thread to start
while (!started.get()) {
Thread.sleep(10);
}
Thread carrier = carrierRef.get();
long carrierId = carrier.threadId();
long vthreadId = vthread.threadId();
// carrier thread should be on WAITING on virtual thread
ThreadInfo ti = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId);
Thread.State state = ti.getThreadState();
LockInfo lockInfo = ti.getLockInfo();
assertEquals(Thread.State.WAITING, state);
assertNotNull(lockInfo);
assertEquals(vthread.getClass().getName(), lockInfo.getClassName());
assertEquals(System.identityHashCode(vthread), lockInfo.getIdentityHashCode());
assertEquals(vthreadId, ti.getLockOwnerId());
} finally {
done.set(true);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,7 @@
* @summary Test virtual threads using a custom scheduler
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit CustomScheduler
*/
@ -35,9 +36,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
import jdk.test.lib.thread.VThreadRunner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
@ -65,10 +69,13 @@ class CustomScheduler {
*/
@Test
void testCustomScheduler1() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>();
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
ref.set(scheduler(Thread.currentThread()));
}).join();
var ref = new AtomicReference<Executor>();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
Thread thread = factory.newThread(() -> {
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
});
thread.start();
thread.join();
assertTrue(ref.get() == scheduler1);
}
@ -77,17 +84,7 @@ class CustomScheduler {
*/
@Test
void testCustomScheduler2() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>();
Thread.ofVirtual().start(() -> {
try {
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
ref.set(scheduler(Thread.currentThread()));
}).join();
} catch (Exception e) {
e.printStackTrace();
}
}).join();
assertTrue(ref.get() == scheduler1);
VThreadRunner.run(this::testCustomScheduler1);
}
/**
@ -96,16 +93,19 @@ class CustomScheduler {
*/
@Test
void testCustomScheduler3() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>();
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
var ref = new AtomicReference<Executor>();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
Thread thread = factory.newThread(() -> {
try {
Thread.ofVirtual().start(() -> {
ref.set(scheduler(Thread.currentThread()));
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
}).join();
} catch (Exception e) {
e.printStackTrace();
}
}).join();
});
thread.start();
thread.join();
assertTrue(ref.get() == scheduler1);
}
@ -115,16 +115,22 @@ class CustomScheduler {
*/
@Test
void testCustomScheduler4() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>();
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
var ref = new AtomicReference<Executor>();
ThreadFactory factory1 = VThreadScheduler.virtualThreadFactory(scheduler1);
ThreadFactory factory2 = VThreadScheduler.virtualThreadFactory(scheduler2);
Thread thread1 = factory1.newThread(() -> {
try {
ThreadBuilders.virtualThreadBuilder(scheduler2).start(() -> {
ref.set(scheduler(Thread.currentThread()));
}).join();
Thread thread2 = factory2.newThread(() -> {
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
});
thread2.start();
thread2.join();
} catch (Exception e) {
e.printStackTrace();
}
}).join();
});
thread1.start();
thread1.join();
assertTrue(ref.get() == scheduler2);
}
@ -149,8 +155,9 @@ class CustomScheduler {
}
assertTrue(exc.get() instanceof WrongThreadException);
};
ThreadBuilders.virtualThreadBuilder(scheduler).start(LockSupport::park);
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread thread = factory.newThread(LockSupport::park);
thread.start();
}
/**
@ -162,11 +169,12 @@ class CustomScheduler {
Thread carrier = Thread.currentThread();
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
try {
var builder = ThreadBuilders.virtualThreadBuilder(Runnable::run);
Thread vthread = builder.start(() -> {
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
Thread vthread = factory.newThread(() -> {
Thread.currentThread().interrupt();
Thread.yield();
});
vthread.start();
assertTrue(vthread.isInterrupted());
assertFalse(carrier.isInterrupted());
} finally {
@ -183,10 +191,11 @@ class CustomScheduler {
Thread carrier = Thread.currentThread();
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
try {
var builder = ThreadBuilders.virtualThreadBuilder(Runnable::run);
Thread vthread = builder.start(() -> {
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
Thread vthread = factory.newThread(() -> {
Thread.currentThread().interrupt();
});
vthread.start();
assertTrue(vthread.isInterrupted());
assertFalse(carrier.isInterrupted());
} finally {
@ -204,11 +213,13 @@ class CustomScheduler {
Thread.currentThread().interrupt();
task.run();
};
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
try {
AtomicBoolean interrupted = new AtomicBoolean();
Thread vthread = ThreadBuilders.virtualThreadBuilder(scheduler).start(() -> {
Thread vthread = factory.newThread(() -> {
interrupted.set(Thread.currentThread().isInterrupted());
});
vthread.start();
assertFalse(vthread.isInterrupted());
} finally {
Thread.interrupted();
@ -216,18 +227,60 @@ class CustomScheduler {
}
/**
* Returns the scheduler for the given virtual thread.
* Test custom scheduler throwing OOME when starting a thread.
*/
private static Executor scheduler(Thread thread) {
if (!thread.isVirtual())
throw new IllegalArgumentException("Not a virtual thread");
try {
Field scheduler = Class.forName("java.lang.VirtualThread")
.getDeclaredField("scheduler");
scheduler.setAccessible(true);
return (Executor) scheduler.get(thread);
} catch (Exception e) {
throw new RuntimeException(e);
@Test
void testThreadStartOOME() throws Exception {
Executor scheduler = task -> {
System.err.println("OutOfMemoryError");
throw new OutOfMemoryError();
};
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread thread = factory.newThread(() -> { });
assertThrows(OutOfMemoryError.class, thread::start);
}
/**
* Test custom scheduler throwing OOME when unparking a thread.
*/
@Test
void testThreadUnparkOOME() throws Exception {
try (ExecutorService executor = Executors.newFixedThreadPool(1)) {
AtomicInteger counter = new AtomicInteger();
Executor scheduler = task -> {
switch (counter.getAndIncrement()) {
case 0 -> executor.execute(task); // Thread.start
case 1, 2 -> { // unpark attempt 1+2
System.err.println("OutOfMemoryError");
throw new OutOfMemoryError();
}
default -> executor.execute(task);
}
executor.execute(task);
};
// start thread and wait for it to park
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
var thread = factory.newThread(LockSupport::park);
thread.start();
await(thread, Thread.State.WAITING);
// unpark thread, this should retry until OOME is not thrown
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Waits for the given thread to reach a given state.
*/
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
Thread.State state = thread.getState();
while (state != expectedState) {
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
Thread.sleep(10);
state = thread.getState();
}
}
}

View File

@ -1,129 +0,0 @@
/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Test Thread.getStackTrace to examine the stack trace of a virtual
* thread and its carrier
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @run main GetStackTrace
*/
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedTransferQueue;
import java.util.stream.Stream;
public class GetStackTrace {
private static final Object LOCK = new Object();
public static void main(String[] args) throws Exception {
try (var scheduler = new Scheduler()) {
Thread vthread = scheduler.startVirtualThread(() -> {
synchronized (LOCK) {
try {
LOCK.wait();
} catch (InterruptedException e) { }
}
});
try {
// wait for virtual thread to wait
while (vthread.getState() != Thread.State.WAITING) {
Thread.sleep(10);
}
// bottom-most frame of virtual thread should be VirtualThread.run
System.out.println(vthread);
StackTraceElement[] vthreadStack = vthread.getStackTrace();
Stream.of(vthreadStack).forEach(System.out::println);
assertEquals("run", vthreadStack[vthreadStack.length - 1].getMethodName());
System.out.println();
// top-most frame of carrier thread should be Continuation.run
// bottom-most frame of carrier thread should be Thread.run
var carrier = scheduler.thread();
System.out.println(carrier);
StackTraceElement[] carrierStack = carrier.getStackTrace();
Stream.of(carrierStack).forEach(System.out::println);
assertEquals("run", carrierStack[0].getMethodName());
assertEquals("run", carrierStack[carrierStack.length - 1].getMethodName());
} finally {
vthread.interrupt();
}
}
}
/**
* A scheduler with one thread.
*/
private static class Scheduler implements AutoCloseable, Executor {
private final BlockingQueue<Runnable> tasks = new LinkedTransferQueue<>();
private final Thread thread;
private volatile boolean done;
Scheduler() {
this.thread = Thread.ofPlatform().start(() -> {
try {
while (!done) {
Runnable task = tasks.take();
task.run();
}
} catch (InterruptedException e) { }
});
}
Thread thread() {
return thread;
}
@Override
public void close() throws InterruptedException {
done = true;
thread.interrupt();
thread.join();
}
@Override
public void execute(Runnable task) {
tasks.add(task);
}
Thread startVirtualThread(Runnable task) {
return ThreadBuilders.virtualThreadBuilder(this).start(task);
}
}
private static void assertTrue(boolean e) {
if (!e) throw new RuntimeException();
}
private static void assertEquals(Object x, Object y) {
if (!Objects.equals(x, y))
throw new RuntimeException();
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Test Thread::getStackTrace on a virtual thread that is runnable-unmounted
* @requires vm.continuations
* @run main/othervm -Djdk.virtualThreadScheduler.maxPoolSize=1 GetStackTraceWhenRunnable
*/
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class GetStackTraceWhenRunnable {
public static void main(String[] args) throws Exception {
// start thread1 and wait for it to park
Thread thread1 = Thread.startVirtualThread(LockSupport::park);
while (thread1.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
// start thread2 to pin the carrier thread
var started = new AtomicBoolean();
var done = new AtomicBoolean();
Thread thread2 = Thread.startVirtualThread(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
// wait for thread2 to start
while (!started.get()) {
Thread.sleep(10);
}
// unpark thread1 and check that it is "stuck" in the runnable state
// (the carrier thread is pinned, no other virtual thread can run)
LockSupport.unpark(thread1);
for (int i = 0; i < 5; i++) {
assertTrue(thread1.getState() == Thread.State.RUNNABLE);
Thread.sleep(100);
}
// print thread1's stack trace
StackTraceElement[] stack = thread1.getStackTrace();
assertTrue(stack.length > 0);
for (StackTraceElement e : stack) {
System.out.println(e);
}
} finally {
done.set(true);
thread2.join();
thread1.join();
}
}
static void assertTrue(boolean e) {
if (!e) throw new RuntimeException();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -40,9 +40,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -52,15 +50,25 @@ import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile;
import jdk.test.lib.thread.VThreadPinner;
import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class JfrEvents {
@BeforeAll
static void setup() {
int minParallelism = 2;
if (Thread.currentThread().isVirtual()) {
minParallelism++;
}
VThreadRunner.ensureParallelism(minParallelism);
}
/**
* Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
*/
@ -93,100 +101,42 @@ class JfrEvents {
}
/**
* Arguments for testVirtualThreadPinned to test jdk.VirtualThreadPinned event.
* [0] label/description
* [1] the operation to park/wait
* [2] the Thread.State when parked/waiting
* [3] the action to unpark/notify the thread
*/
static Stream<Arguments> pinnedCases() {
Object lock = new Object();
// park with native frame on stack
var finish1 = new AtomicBoolean();
var parkWhenPinned = Arguments.of(
"LockSupport.park when pinned",
(ThrowingRunnable<Exception>) () -> {
VThreadPinner.runPinned(() -> {
while (!finish1.get()) {
LockSupport.park();
}
});
},
Thread.State.WAITING,
(Consumer<Thread>) t -> {
finish1.set(true);
LockSupport.unpark(t);
}
);
// timed park with native frame on stack
var finish2 = new AtomicBoolean();
var timedParkWhenPinned = Arguments.of(
"LockSupport.parkNanos when pinned",
(ThrowingRunnable<Exception>) () -> {
VThreadPinner.runPinned(() -> {
while (!finish2.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
},
Thread.State.TIMED_WAITING,
(Consumer<Thread>) t -> {
finish2.set(true);
LockSupport.unpark(t);
}
);
return Stream.of(parkWhenPinned, timedParkWhenPinned);
}
/**
* Test jdk.VirtualThreadPinned event.
* Test jdk.VirtualThreadPinned event when parking while pinned.
*/
@ParameterizedTest
@MethodSource("pinnedCases")
void testVirtualThreadPinned(String label,
ThrowingRunnable<Exception> parker,
Thread.State expectedState,
Consumer<Thread> unparker) throws Exception {
@ValueSource(booleans = { true, false })
void testParkWhenPinned(boolean timed) throws Exception {
try (Recording recording = new Recording()) {
recording.enable("jdk.VirtualThreadPinned");
recording.start();
try {
var exception = new AtomicReference<Throwable>();
var thread = Thread.ofVirtual().start(() -> {
try {
parker.run();
} catch (Throwable e) {
exception.set(e);
var started = new AtomicBoolean();
var done = new AtomicBoolean();
var vthread = Thread.startVirtualThread(() -> {
VThreadPinner.runPinned(() -> {
started.set(true);
while (!done.get()) {
if (timed) {
LockSupport.parkNanos(Long.MAX_VALUE);
} else {
LockSupport.park();
}
}
});
try {
// wait for thread to park/wait
Thread.State state = thread.getState();
while (state != expectedState) {
assertTrue(state != Thread.State.TERMINATED, thread.toString());
Thread.sleep(10);
state = thread.getState();
}
} finally {
unparker.accept(thread);
thread.join();
assertNull(exception.get());
}
});
try {
// wait for thread to start and park
awaitTrue(started);
await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
} finally {
done.set(true);
LockSupport.unpark(vthread);
vthread.join();
recording.stop();
}
Map<String, Integer> events = sumEvents(recording);
System.err.println(events);
// should have at least one pinned event
int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0);
assertTrue(pinnedCount >= 1, "Expected one or more events");
assertContainsPinnedEvent(recording, vthread);
}
}
@ -203,16 +153,14 @@ class JfrEvents {
Executor scheduler = task -> pool.execute(task);
// create virtual thread that uses custom scheduler
ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler).factory();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
// start a thread
Thread thread = factory.newThread(LockSupport::park);
thread.start();
// wait for thread to park
while (thread.getState() != Thread.State.WAITING) {
Thread.sleep(10);
}
await(thread, Thread.State.WAITING);
// shutdown scheduler
pool.shutdown();
@ -232,14 +180,23 @@ class JfrEvents {
recording.stop();
}
Map<String, Integer> events = sumEvents(recording);
System.err.println(events);
int count = events.getOrDefault("jdk.VirtualThreadSubmitFailed", 0);
assertEquals(2, count);
List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
System.err.println(submitFailedEvents);
assertTrue(submitFailedEvents.size() == 2, "Expected two events");
}
}
/**
* Returns the list of events in the given recording with the given name.
*/
private static List<RecordedEvent> find(Recording recording, String name) throws IOException {
Path recordingFile = recordingFile(recording);
return RecordingFile.readAllEvents(recordingFile)
.stream()
.filter(e -> e.getEventType().getName().equals(name))
.toList();
}
/**
* Read the events from the recording and return a map of event name to count.
*/
@ -264,4 +221,38 @@ class JfrEvents {
}
return recordingFile;
}
/**
* Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread.
*/
private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException {
List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned");
assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording");
System.err.println(pinnedEvents);
long tid = thread.threadId();
assertTrue(pinnedEvents.stream()
.anyMatch(e -> e.getThread().getJavaThreadId() == tid),
"jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found");
}
/**
* Waits for the given boolean to be set to true.
*/
private void awaitTrue(AtomicBoolean b) throws InterruptedException {
while (!b.get()) {
Thread.sleep(10);
}
}
/**
* Waits for the given thread to reach a given state.
*/
private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
Thread.State state = thread.getState();
while (state != expectedState) {
Thread.sleep(10);
state = thread.getState();
}
}
}

View File

@ -0,0 +1,410 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=default
* @summary Test virtual thread with monitor enter/exit
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
*/
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import jdk.test.lib.thread.VThreadPinner;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.api.condition.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
class MonitorEnterExit {
static final int MAX_ENTER_DEPTH = 256;
@BeforeAll
static void setup() {
// need >=2 carriers for testing pinning when main thread is a virtual thread
if (Thread.currentThread().isVirtual()) {
VThreadRunner.ensureParallelism(2);
}
}
/**
* Test monitor enter with no contention.
*/
@Test
void testEnterNoContention() throws Exception {
var lock = new Object();
VThreadRunner.run(() -> {
synchronized (lock) {
assertTrue(Thread.holdsLock(lock));
}
assertFalse(Thread.holdsLock(lock));
});
}
/**
* Test monitor enter with contention, monitor is held by platform thread.
*/
@Test
void testEnterWhenHeldByPlatformThread() throws Exception {
testEnterWithContention();
}
/**
* Test monitor enter with contention, monitor is held by virtual thread.
*/
@Test
void testEnterWhenHeldByVirtualThread() throws Exception {
VThreadRunner.run(this::testEnterWithContention);
}
/**
* Test monitor enter with contention, monitor will be held by caller thread.
*/
private void testEnterWithContention() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var entered = new AtomicBoolean();
var vthread = Thread.ofVirtual().unstarted(() -> {
started.countDown();
synchronized (lock) {
assertTrue(Thread.holdsLock(lock));
entered.set(true);
}
assertFalse(Thread.holdsLock(lock));
});
try {
synchronized (lock) {
vthread.start();
// wait for thread to start and block
started.await();
await(vthread, Thread.State.BLOCKED);
assertFalse(entered.get());
}
} finally {
vthread.join();
}
assertTrue(entered.get());
}
/**
* Test monitor reenter.
*/
@Test
void testReenter() throws Exception {
var lock = new Object();
VThreadRunner.run(() -> {
testReenter(lock, 0);
assertFalse(Thread.holdsLock(lock));
});
}
private void testReenter(Object lock, int depth) {
if (depth < MAX_ENTER_DEPTH) {
synchronized (lock) {
assertTrue(Thread.holdsLock(lock));
testReenter(lock, depth + 1);
assertTrue(Thread.holdsLock(lock));
}
}
}
/**
* Test monitor enter when pinned.
*/
@Test
void testEnterWhenPinned() throws Exception {
var lock = new Object();
VThreadPinner.runPinned(() -> {
synchronized (lock) {
assertTrue(Thread.holdsLock(lock));
}
assertFalse(Thread.holdsLock(lock));
});
}
/**
* Test monitor reenter when pinned.
*/
@Test
void testReenterWhenPinned() throws Exception {
VThreadRunner.run(() -> {
var lock = new Object();
synchronized (lock) {
VThreadPinner.runPinned(() -> {
assertTrue(Thread.holdsLock(lock));
synchronized (lock) {
assertTrue(Thread.holdsLock(lock));
}
assertTrue(Thread.holdsLock(lock));
});
}
assertFalse(Thread.holdsLock(lock));
});
}
/**
* Test contended monitor enter when pinned. Monitor is held by platform thread.
*/
@Test
void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
testEnterWithContentionWhenPinned();
}
/**
* Test contended monitor enter when pinned. Monitor is held by virtual thread.
*/
@Test
void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
// need at least two carrier threads
int previousParallelism = VThreadRunner.ensureParallelism(2);
try {
VThreadRunner.run(this::testEnterWithContentionWhenPinned);
} finally {
VThreadRunner.setParallelism(previousParallelism);
}
}
/**
* Test contended monitor enter when pinned, monitor will be held by caller thread.
*/
private void testEnterWithContentionWhenPinned() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var entered = new AtomicBoolean();
Thread vthread = Thread.ofVirtual().unstarted(() -> {
VThreadPinner.runPinned(() -> {
started.countDown();
synchronized (lock) {
entered.set(true);
}
});
});
synchronized (lock) {
// start thread and wait for it to block
vthread.start();
started.await();
await(vthread, Thread.State.BLOCKED);
assertFalse(entered.get());
}
vthread.join();
// check thread entered monitor
assertTrue(entered.get());
}
/**
* Returns a stream of elements that are ordered pairs of platform and virtual thread
* counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
*/
static Stream<Arguments> threadCounts() {
return IntStream.range(0, 17)
.filter(i -> i % 2 == 0)
.mapToObj(i -> i)
.flatMap(np -> IntStream.range(2, 33)
.filter(i -> i % 2 == 0)
.mapToObj(vp -> Arguments.of(np, vp)));
}
/**
* Test mutual exclusion of monitors with platform and virtual threads.
*/
@ParameterizedTest
@MethodSource("threadCounts")
void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
class Counter {
int count;
synchronized void increment() {
count++;
Thread.yield();
}
}
var counter = new Counter();
int nThreads = nPlatformThreads + nVirtualThreads;
var threads = new Thread[nThreads];
int index = 0;
for (int i = 0; i < nPlatformThreads; i++) {
threads[index] = Thread.ofPlatform()
.name("platform-" + index)
.unstarted(counter::increment);
index++;
}
for (int i = 0; i < nVirtualThreads; i++) {
threads[index] = Thread.ofVirtual()
.name("virtual-" + index)
.unstarted(counter::increment);
index++;
}
// start all threads
for (Thread thread : threads) {
thread.start();
}
// wait for all threads to terminate
for (Thread thread : threads) {
thread.join();
}
assertEquals(nThreads, counter.count);
}
/**
* Test unblocking a virtual thread waiting to enter a monitor held by a platform thread.
*/
@RepeatedTest(20)
void testUnblockingByPlatformThread() throws Exception {
testUnblocking();
}
/**
* Test unblocking a virtual thread waiting to enter a monitor held by another
* virtual thread.
*/
@RepeatedTest(20)
void testUnblockingByVirtualThread() throws Exception {
VThreadRunner.run(this::testUnblocking);
}
/**
* Test unblocking a virtual thread waiting to enter a monitor, monitor will be
* initially be held by caller thread.
*/
private void testUnblocking() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var entered = new AtomicBoolean();
var vthread = Thread.ofVirtual().unstarted(() -> {
started.countDown();
synchronized (lock) {
entered.set(true);
}
});
try {
synchronized (lock) {
vthread.start();
started.await();
// random delay before exiting monitor
switch (ThreadLocalRandom.current().nextInt(4)) {
case 0 -> { /* no delay */}
case 1 -> Thread.onSpinWait();
case 2 -> Thread.yield();
case 3 -> await(vthread, Thread.State.BLOCKED);
default -> fail();
}
assertFalse(entered.get());
}
} finally {
vthread.join();
}
assertTrue(entered.get());
}
/**
* Test that unblocking a virtual thread waiting to enter a monitor does not consume
* the thread's parking permit.
*/
@Test
void testParkingPermitNotConsumed() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var vthread = Thread.ofVirtual().unstarted(() -> {
started.countDown();
LockSupport.unpark(Thread.currentThread());
synchronized (lock) { } // should block
LockSupport.park(); // should not park
});
synchronized (lock) {
vthread.start();
// wait for thread to start and block
started.await();
await(vthread, Thread.State.BLOCKED);
}
vthread.join();
}
/**
* Test that unblocking a virtual thread waiting to enter a monitor does not make
* available the thread's parking permit.
*/
@Test
void testParkingPermitNotOffered() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var vthread = Thread.ofVirtual().unstarted(() -> {
started.countDown();
synchronized (lock) { } // should block
LockSupport.park(); // should park
});
synchronized (lock) {
vthread.start();
// wait for thread to start and block
started.await();
await(vthread, Thread.State.BLOCKED);
}
try {
// wait for thread to park, it should not terminate
await(vthread, Thread.State.WAITING);
vthread.join(Duration.ofMillis(100));
assertEquals(Thread.State.WAITING, vthread.getState());
} finally {
LockSupport.unpark(vthread);
vthread.join();
}
}
/**
* Waits for the given thread to reach a given state.
*/
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
Thread.State state = thread.getState();
while (state != expectedState) {
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
Thread.sleep(10);
state = thread.getState();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,41 +21,78 @@
* questions.
*/
/**
* @test
/*
* @test id=default
* @summary Test virtual threads using Object.wait/notifyAll
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit MonitorWaitNotify
* @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorWaitNotify
*/
import java.util.concurrent.Semaphore;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import jdk.test.lib.thread.VThreadScheduler;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
class MonitorWaitNotify {
@BeforeAll
static void setup() {
// need >=2 carriers for testing pinning
VThreadRunner.ensureParallelism(2);
}
/**
* Test virtual thread waits, notified by platform thread.
*/
@Test
void testWaitNotify1() throws Exception {
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testWaitNotify1(boolean pinned) throws Exception {
var lock = new Object();
var ready = new Semaphore(0);
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
ready.release();
try {
lock.wait();
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait();
});
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) { }
}
});
// thread invokes notify
ready.acquire();
awaitTrue(ready);
// notify, thread should block waiting to reenter
synchronized (lock) {
lock.notifyAll();
await(thread, Thread.State.BLOCKED);
}
thread.join();
}
@ -66,15 +103,13 @@ class MonitorWaitNotify {
@Test
void testWaitNotify2() throws Exception {
var lock = new Object();
var ready = new Semaphore(0);
var thread = Thread.ofVirtual().start(() -> {
ready.acquireUninterruptibly();
var thread = Thread.ofVirtual().unstarted(() -> {
synchronized (lock) {
lock.notifyAll();
}
});
synchronized (lock) {
ready.release();
thread.start();
lock.wait();
}
thread.join();
@ -83,89 +118,447 @@ class MonitorWaitNotify {
/**
* Test virtual thread waits, notified by another virtual thread.
*/
@Test
void testWaitNotify3() throws Exception {
// need at least two carrier threads due to pinning
int previousParallelism = VThreadRunner.ensureParallelism(2);
try {
var lock = new Object();
var ready = new Semaphore(0);
var thread1 = Thread.ofVirtual().start(() -> {
synchronized (lock) {
ready.release();
try {
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testWaitNotify3(boolean pinned) throws Exception {
var lock = new Object();
var ready = new AtomicBoolean();
var thread1 = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait();
});
} else {
ready.set(true);
lock.wait();
} catch (InterruptedException e) { }
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
var thread2 = Thread.ofVirtual().start(() -> {
ready.acquireUninterruptibly();
}
});
var thread2 = Thread.ofVirtual().start(() -> {
try {
awaitTrue(ready);
// notify, thread should block waiting to reenter
synchronized (lock) {
lock.notifyAll();
await(thread1, Thread.State.BLOCKED);
}
});
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.join();
thread2.join();
}
/**
* Test notifyAll when there are no threads waiting.
*/
@ParameterizedTest
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
void testNotifyBeforeWait(int timeout) throws Exception {
var lock = new Object();
// no threads waiting
synchronized (lock) {
lock.notifyAll();
}
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
try {
synchronized (lock) {
ready.set(true);
// thread should wait
if (timeout > 0) {
lock.wait(timeout);
} else {
lock.wait();
}
}
} catch (InterruptedException e) { }
});
try {
// wait for thread to start and wait
awaitTrue(ready);
Thread.State expectedState = timeout > 0
? Thread.State.TIMED_WAITING
: Thread.State.WAITING;
await(thread, expectedState);
// poll thread state again, it should still be waiting
Thread.sleep(10);
assertEquals(thread.getState(), expectedState);
} finally {
// restore
VThreadRunner.setParallelism(previousParallelism);
synchronized (lock) {
lock.notifyAll();
}
thread.join();
}
}
/**
* Test duration of timed Object.wait.
*/
@Test
void testTimedWaitDuration1() throws Exception {
var lock = new Object();
var durationRef = new AtomicReference<Long>();
var thread = Thread.ofVirtual().start(() -> {
try {
synchronized (lock) {
long start = millisTime();
lock.wait(2000);
durationRef.set(millisTime() - start);
}
} catch (InterruptedException e) { }
});
thread.join();
long duration = durationRef.get();
checkDuration(duration, 1900, 20_000);
}
/**
* Test duration of timed Object.wait. This test invokes wait twice, first with a short
* timeout, the second with a longer timeout. The test scenario ensures that the
* timeout from the first wait doesn't interfere with the second wait.
*/
@Test
void testTimedWaitDuration2() throws Exception {
var lock = new Object();
var ready = new AtomicBoolean();
var waited = new AtomicBoolean();
var durationRef = new AtomicReference<Long>();
var thread = Thread.ofVirtual().start(() -> {
try {
synchronized (lock) {
ready.set(true);
lock.wait(200);
waited.set(true);
long start = millisTime();
lock.wait(2000);
durationRef.set(millisTime() - start);
}
} catch (InterruptedException e) { }
});
awaitTrue(ready);
synchronized (lock) {
// wake thread if waiting in first wait
if (!waited.get()) {
lock.notifyAll();
}
}
thread.join();
long duration = durationRef.get();
checkDuration(duration, 1900, 20_000);
}
/**
* Testing invoking Object.wait with interrupt status set.
*/
@ParameterizedTest
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
void testWaitWithInterruptSet(int timeout) throws Exception {
VThreadRunner.run(() -> {
Object lock = new Object();
synchronized (lock) {
Thread.currentThread().interrupt();
if (timeout > 0) {
assertThrows(InterruptedException.class, () -> lock.wait(timeout));
} else {
assertThrows(InterruptedException.class, lock::wait);
}
assertFalse(Thread.currentThread().isInterrupted());
}
});
}
/**
* Test interrupting a virtual thread waiting in Object.wait.
*/
@ParameterizedTest
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
void testInterruptWait(int timeout) throws Exception {
var lock = new Object();
var ready = new AtomicBoolean();
var interruptedException = new AtomicBoolean();
var vthread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try {
ready.set(true);
if (timeout > 0) {
lock.wait(timeout);
} else {
lock.wait();
}
} catch (InterruptedException e) {
// check stack trace has the expected frames
Set<String> expected = Set.of("wait0", "wait", "run");
Set<String> methods = Stream.of(e.getStackTrace())
.map(StackTraceElement::getMethodName)
.collect(Collectors.toSet());
assertTrue(methods.containsAll(expected));
interruptedException.set(true);
}
}
});
// wait for thread to start and wait
awaitTrue(ready);
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
// interrupt thread, should block, then throw InterruptedException
synchronized (lock) {
vthread.interrupt();
await(vthread, Thread.State.BLOCKED);
}
vthread.join();
assertTrue(interruptedException.get());
}
/**
* Test interrupting a virtual thread blocked waiting to reenter after waiting.
*/
@ParameterizedTest
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
void testInterruptReenterAfterWait(int timeout) throws Exception {
var lock = new Object();
var ready = new AtomicBoolean();
var interruptedException = new AtomicBoolean();
var vthread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try {
ready.set(true);
if (timeout > 0) {
lock.wait(timeout);
} else {
lock.wait();
}
} catch (InterruptedException e) {
interruptedException.set(true);
}
}
});
// wait for thread to start and wait
awaitTrue(ready);
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
// notify, thread should block waiting to reenter
synchronized (lock) {
lock.notifyAll();
await(vthread, Thread.State.BLOCKED);
// interrupt when blocked
vthread.interrupt();
}
vthread.join();
assertFalse(interruptedException.get());
assertTrue(vthread.isInterrupted());
}
/**
* Test Object.wait when the monitor entry count > 1.
*/
@ParameterizedTest
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
void testWaitWhenEnteredManyTimes(int timeout) throws Exception {
var lock = new Object();
var ready = new AtomicBoolean();
var vthread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
synchronized (lock) {
synchronized (lock) {
try {
ready.set(true);
if (timeout > 0) {
lock.wait(timeout);
} else {
lock.wait();
}
} catch (InterruptedException e) { }
}
}
}
});
// wait for thread to start and wait
awaitTrue(ready);
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
// notify, thread should block waiting to reenter
synchronized (lock) {
lock.notifyAll();
await(vthread, Thread.State.BLOCKED);
}
vthread.join();
}
/**
* Test that Object.wait does not consume the thread's parking permit.
*/
@Test
void testParkingPermitNotConsumed() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var completed = new AtomicBoolean();
var vthread = Thread.ofVirtual().start(() -> {
started.countDown();
LockSupport.unpark(Thread.currentThread());
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
fail("wait interrupted");
}
}
LockSupport.park(); // should not park
completed.set(true);
});
// wait for thread to start and wait
started.await();
await(vthread, Thread.State.WAITING);
// wakeup thread
synchronized (lock) {
lock.notifyAll();
}
// thread should terminate
vthread.join();
assertTrue(completed.get());
}
/**
* Test that Object.wait does not make available the thread's parking permit.
*/
@Test
void testParkingPermitNotOffered() throws Exception {
var lock = new Object();
var started = new CountDownLatch(1);
var readyToPark = new CountDownLatch(1);
var completed = new AtomicBoolean();
var vthread = Thread.ofVirtual().start(() -> {
started.countDown();
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
fail("wait interrupted");
}
}
readyToPark.countDown();
LockSupport.park(); // should park
completed.set(true);
});
// wait for thread to start and wait
started.await();
await(vthread, Thread.State.WAITING);
// wakeup thread
synchronized (lock) {
lock.notifyAll();
}
// thread should park
readyToPark.await();
await(vthread, Thread.State.WAITING);
LockSupport.unpark(vthread);
// thread should terminate
vthread.join();
assertTrue(completed.get());
}
/**
* Test that wait(long) throws IAE when timeout is negative.
*/
@Test
void testIllegalArgumentException() throws Exception {
VThreadRunner.run(() -> {
Object obj = new Object();
synchronized (obj) {
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L));
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L));
assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE));
}
});
}
/**
* Test that wait throws IMSE when not owner.
*/
@Test
void testIllegalMonitorStateException() throws Exception {
VThreadRunner.run(() -> {
Object obj = new Object();
assertThrows(IllegalMonitorStateException.class, () -> obj.wait());
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0));
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000));
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE));
});
}
/**
* Waits for the boolean value to become true.
*/
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
while (!ref.get()) {
Thread.sleep(20);
}
}
/**
* Test interrupt status set when calling Object.wait.
* Waits for the given thread to reach a given state.
*/
@Test
void testWaitNotify4() throws Exception {
VThreadRunner.run(() -> {
Thread t = Thread.currentThread();
t.interrupt();
Object lock = new Object();
synchronized (lock) {
try {
lock.wait();
fail();
} catch (InterruptedException e) {
// interrupt status should be cleared
assertFalse(t.isInterrupted());
}
}
});
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
Thread.State state = thread.getState();
while (state != expectedState) {
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
Thread.sleep(10);
state = thread.getState();
}
}
/**
* Test interrupt when blocked in Object.wait.
* Returns the current time in milliseconds.
*/
@Test
void testWaitNotify5() throws Exception {
VThreadRunner.run(() -> {
Thread t = Thread.currentThread();
scheduleInterrupt(t, 1000);
Object lock = new Object();
synchronized (lock) {
try {
lock.wait();
fail();
} catch (InterruptedException e) {
// interrupt status should be cleared
assertFalse(t.isInterrupted());
}
}
});
private static long millisTime() {
long now = System.nanoTime();
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
}
/**
* Schedule a thread to be interrupted after a delay.
* Check a duration is within expected bounds.
* @param duration, in milliseconds
* @param min minimum expected duration, in milliseconds
* @param max maximum expected duration, in milliseconds
* @return the duration (now - start), in milliseconds
*/
private static void scheduleInterrupt(Thread thread, long delay) {
Runnable interruptTask = () -> {
try {
Thread.sleep(delay);
thread.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
};
new Thread(interruptTask).start();
private static void checkDuration(long duration, long min, long max) {
assertTrue(duration >= min,
"Duration " + duration + "ms, expected >= " + min + "ms");
assertTrue(duration <= max,
"Duration " + duration + "ms, expected <= " + max + "ms");
}
}

View File

@ -26,12 +26,14 @@
* @summary Test virtual thread park when scheduler is a fixed thread pool
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main ParkWithFixedThreadPool
*/
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
public class ParkWithFixedThreadPool {
public static void main(String[] args) throws Exception {
@ -58,9 +60,7 @@ public class ParkWithFixedThreadPool {
}
};
ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler)
.name("vthread-", 0)
.factory();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
for (int i = 0; i < vthreadCount; i++) {
vthreads[i] = factory.newThread(target);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -38,6 +38,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
import jdk.test.lib.thread.VThreadRunner;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@ -142,11 +143,10 @@ class Reflection {
*/
@Test
void testInvokeStatic6() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
Method parkMethod = Parker.class.getDeclaredMethod("park");
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
ThreadFactory factory = builder.factory();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
var ready = new CountDownLatch(1);
Thread vthread = factory.newThread(() -> {
@ -321,11 +321,10 @@ class Reflection {
*/
@Test
void testNewInstance6() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
Constructor<?> ctor = Parker.class.getDeclaredConstructor();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
ThreadFactory factory = builder.factory();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
var ready = new CountDownLatch(1);
Thread vthread = factory.newThread(() -> {

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Test stack traces in exceptions, stack frames walked by the StackWalker,
* and the stack trace returned by Thread.getStackTrace
* @requires vm.continuations
* @modules java.base/java.lang:+open java.management
* @library /test/lib
* @run junit StackFrames
* @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowCarrierFrames StackFrames
*/
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static java.lang.StackWalker.Option.*;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StackFrames {
/**
* Test that the stack trace in exceptions does not include the carrier thread
* frames, except when running with -XX:+ShowCarrierFrames.
*/
@Test
void testStackTraceException() throws Exception {
VThreadRunner.run(() -> {
Exception e = new Exception();
boolean found = Arrays.stream(e.getStackTrace())
.map(StackTraceElement::getClassName)
.anyMatch("java.util.concurrent.ForkJoinPool"::equals);
assertTrue(found == hasJvmArgument("-XX:+ShowCarrierFrames"));
});
}
/**
* Test that StackWalker does not include carrier thread frames in the stream of
* stack frames.
*/
@Test
void testStackWalker() throws Exception {
VThreadRunner.run(() -> {
StackWalker walker = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE));
boolean found = walker.walk(sf ->
sf.map(StackWalker.StackFrame::getDeclaringClass)
.anyMatch(c -> c == ForkJoinPool.class));
assertFalse(found);
});
}
/**
* Test Thread.getStackTrace returns the expected bottom frame for both the carrier
* and virtual thread.
*/
@Test
void testBottomFrames() throws Exception {
try (ForkJoinPool pool = new ForkJoinPool(1)) {
var carrierRef = new AtomicReference<Thread>();
Executor scheduler = task -> {
pool.submit(() -> {
carrierRef.set(Thread.currentThread());
task.run();
});
};
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
// create virtual thread to use custom scheduler
var vthread = factory.newThread(() -> {
ready.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
vthread.start();
try {
awaitTrue(ready);
// get carrier Thread
Thread carrier = carrierRef.get();
assertTrue(carrier instanceof ForkJoinWorkerThread);
// bottom-most frame of virtual thread should be VirtualThread.run
System.err.println(vthread);
StackTraceElement[] vthreadStack = vthread.getStackTrace();
Stream.of(vthreadStack).forEach(e -> System.err.println(" " + e));
StackTraceElement bottomFrame = vthreadStack[vthreadStack.length - 1];
assertEquals("java.lang.VirtualThread.run",
bottomFrame.getClassName() + "." + bottomFrame.getMethodName());
// bottom-most frame of carrier thread should be Thread.run
System.err.println(carrier);
StackTraceElement[] carrierStack = carrier.getStackTrace();
Stream.of(carrierStack).forEach(e -> System.err.println(" " + e));
bottomFrame = carrierStack[carrierStack.length - 1];
assertEquals("java.util.concurrent.ForkJoinWorkerThread.run",
bottomFrame.getClassName() + "." + bottomFrame.getMethodName());
} finally {
done.set(true);
vthread.join();
}
}
}
/**
* Returns true if started with the given VM option.
*/
private static boolean hasJvmArgument(String arg) {
for (String argument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
if (argument.equals(arg)) return true;
}
return false;
}
/**
* Waits for the boolean value to become true.
*/
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
while (!ref.get()) {
Thread.sleep(20);
}
}
}

View File

@ -1,82 +0,0 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Test stack traces in exceptions and stack frames walked by the StackWalker
* API do not include the carrier stack frames
* @requires vm.continuations
* @modules java.management
* @library /test/lib
* @run junit StackTraces
* @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowCarrierFrames StackTraces
*/
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import static java.lang.StackWalker.Option.*;
import jdk.test.lib.thread.VThreadRunner;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StackTraces {
/**
* Test that the stack trace in exceptions does not include the carrier thread
* frames, except when running with -XX:+ShowCarrierFrames.
*/
@Test
void testStackTrace() throws Exception {
VThreadRunner.run(() -> {
Exception e = new Exception();
boolean found = Arrays.stream(e.getStackTrace())
.map(StackTraceElement::getClassName)
.anyMatch("java.util.concurrent.ForkJoinPool"::equals);
assertTrue(found == hasJvmArgument("-XX:+ShowCarrierFrames"));
});
}
/**
* Test that StackWalker does not include carrier thread frames.
*/
@Test
void testStackWalker() throws Exception {
VThreadRunner.run(() -> {
StackWalker walker = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE));
boolean found = walker.walk(sf ->
sf.map(StackWalker.StackFrame::getDeclaringClass)
.anyMatch(c -> c == ForkJoinPool.class));
assertFalse(found);
});
}
private static boolean hasJvmArgument(String arg) {
for (String argument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
if (argument.equals(arg)) return true;
}
return false;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -61,13 +61,16 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;
import java.nio.channels.Selector;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
@ -78,9 +81,12 @@ class ThreadAPI {
private static ScheduledExecutorService scheduler;
@BeforeAll
static void setup() throws Exception {
static void setup() {
ThreadFactory factory = Executors.defaultThreadFactory();
scheduler = Executors.newSingleThreadScheduledExecutor(factory);
// need >=2 carriers for testing pinning
VThreadRunner.ensureParallelism(2);
}
@AfterAll
@ -719,14 +725,7 @@ class ThreadAPI {
*/
@Test
void testJoin34() throws Exception {
// need at least two carrier threads due to pinning
int previousParallelism = VThreadRunner.ensureParallelism(2);
try {
VThreadRunner.run(this::testJoin33);
} finally {
// restore
VThreadRunner.setParallelism(previousParallelism);
}
VThreadRunner.run(this::testJoin33);
}
/**
@ -1084,11 +1083,10 @@ class ThreadAPI {
*/
@Test
void testYield1() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
var list = new CopyOnWriteArrayList<String>();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
ThreadFactory factory = builder.factory();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
var thread = factory.newThread(() -> {
list.add("A");
var child = factory.newThread(() -> {
@ -1112,11 +1110,10 @@ class ThreadAPI {
*/
@Test
void testYield2() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
var list = new CopyOnWriteArrayList<String>();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
ThreadFactory factory = builder.factory();
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
var thread = factory.newThread(() -> {
list.add("A");
var child = factory.newThread(() -> {
@ -1708,50 +1705,59 @@ class ThreadAPI {
*/
@Test
void testGetState4() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
AtomicBoolean completed = new AtomicBoolean();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread t1 = builder.start(() -> {
Thread t2 = builder.unstarted(LockSupport::park);
assertEquals(Thread.State.NEW, t2.getState());
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread thread1 = factory.newThread(() -> {
Thread thread2 = factory.newThread(LockSupport::park);
assertEquals(Thread.State.NEW, thread2.getState());
// start t2 to make it runnable
t2.start();
thread2.start();
try {
assertEquals(Thread.State.RUNNABLE, t2.getState());
assertEquals(Thread.State.RUNNABLE, thread2.getState());
// yield to allow t2 to run and park
Thread.yield();
assertEquals(Thread.State.WAITING, t2.getState());
assertEquals(Thread.State.WAITING, thread2.getState());
} finally {
// unpark t2 to make it runnable again
LockSupport.unpark(t2);
LockSupport.unpark(thread2);
}
// t2 should be runnable (not mounted)
assertEquals(Thread.State.RUNNABLE, t2.getState());
assertEquals(Thread.State.RUNNABLE, thread2.getState());
completed.set(true);
});
t1.join();
thread1.start();
thread1.join();
}
assertTrue(completed.get() == true);
}
/**
* Test Thread::getState when thread is waiting to enter a monitor.
* Test Thread::getState when thread is blocked waiting to enter a monitor.
*/
@Test
void testGetState5() throws Exception {
var started = new CountDownLatch(1);
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetState5(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().unstarted(() -> {
started.countDown();
synchronized (lock) { }
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
synchronized (lock) { }
});
} else {
ready.set(true);
synchronized (lock) { }
}
});
synchronized (lock) {
thread.start();
started.await();
awaitTrue(ready);
// wait for thread to block
await(thread, Thread.State.BLOCKED);
@ -1762,16 +1768,35 @@ class ThreadAPI {
/**
* Test Thread::getState when thread is waiting in Object.wait.
*/
@Test
void testGetState6() throws Exception {
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetState6(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try { lock.wait(); } catch (InterruptedException e) { }
try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait();
});
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to wait
awaitTrue(ready);
await(thread, Thread.State.WAITING);
// notify, thread should block trying to reenter
synchronized (lock) {
lock.notifyAll();
await(thread, Thread.State.BLOCKED);
}
} finally {
thread.interrupt();
thread.join();
@ -1781,18 +1806,35 @@ class ThreadAPI {
/**
* Test Thread::getState when thread is waiting in Object.wait(millis).
*/
@Test
void testGetState7() throws Exception {
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetState7(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try {
lock.wait(Long.MAX_VALUE);
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(Long.MAX_VALUE);
});
} else {
ready.set(true);
lock.wait(Long.MAX_VALUE);
}
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to wait
// wait for thread to timed-wait
awaitTrue(ready);
await(thread, Thread.State.TIMED_WAITING);
// notify, thread should block trying to reenter
synchronized (lock) {
lock.notifyAll();
await(thread, Thread.State.BLOCKED);
}
} finally {
thread.interrupt();
thread.join();
@ -1933,7 +1975,7 @@ class ThreadAPI {
* Test Thread::getStackTrace on unstarted thread.
*/
@Test
void testGetStackTrace1() {
void testGetStackTraceUnstarted() {
var thread = Thread.ofVirtual().unstarted(() -> { });
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(stack.length == 0);
@ -1943,136 +1985,224 @@ class ThreadAPI {
* Test Thread::getStackTrace on thread that has been started but has not run.
*/
@Test
void testGetStackTrace2() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
void testGetStackTraceStarted() throws Exception {
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
Executor scheduler = task -> { };
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread thread = builder.start(() -> { });
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread thread = factory.newThread(() -> { });
thread.start();
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(stack.length == 0);
}
/**
* Test Thread::getStackTrace on running thread.
* Test Thread::getStackTrace on thread that is runnable-mounted.
*/
@Test
void testGetStackTrace3() throws Exception {
var sel = Selector.open();
var thread = Thread.ofVirtual().start(() -> {
try { sel.select(); } catch (Exception e) { }
});
try {
while (!contains(thread.getStackTrace(), "select")) {
assertTrue(thread.isAlive());
Thread.sleep(20);
void testGetStackTraceRunnableMounted() throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
class Foo {
void spinUntilDone() {
ready.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
}
}
Foo foo = new Foo();
var thread = Thread.ofVirtual().start(foo::spinUntilDone);
try {
awaitTrue(ready);
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, Foo.class.getName() + ".spinUntilDone"));
} finally {
sel.close();
done.set(true);
thread.join();
}
}
/**
* Test Thread::getStackTrace on thread waiting in Object.wait.
* Test Thread::getStackTrace on thread that is runnable-unmounted.
*/
@Test
void testGetStackTrace4() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
try (ForkJoinPool pool = new ForkJoinPool(1)) {
AtomicReference<Thread> ref = new AtomicReference<>();
Executor scheduler = task -> {
pool.submit(() -> {
ref.set(Thread.currentThread());
task.run();
});
};
void testGetStackTraceRunnableUnmounted() throws Exception {
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread vthread = builder.start(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (Exception e) { }
// custom scheduler with one carrier thread
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
// start thread1 to park
Thread thread1 = factory.newThread(LockSupport::park);
thread1.start();
await(thread1, Thread.State.WAITING);
// start thread2 to spin and pin the carrier thread
var started = new AtomicBoolean();
var done = new AtomicBoolean();
Thread thread2 = factory.newThread(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
thread2.start();
awaitTrue(started);
// get carrier Thread
Thread carrier;
while ((carrier = ref.get()) == null) {
Thread.sleep(20);
try {
// unpark thread1, it should be "stuck" in runnable state
// (the carrier thread is pinned, no other virtual thread can run)
LockSupport.unpark(thread1);
assertEquals(Thread.State.RUNNABLE, thread1.getState());
// print thread1's stack trace
StackTraceElement[] stack = thread1.getStackTrace();
assertTrue(contains(stack, "LockSupport.park"));
} finally {
done.set(true);
}
// wait for virtual thread to block in wait
await(vthread, Thread.State.WAITING);
// get stack trace of both carrier and virtual thread
StackTraceElement[] carrierStackTrace = carrier.getStackTrace();
StackTraceElement[] vthreadStackTrace = vthread.getStackTrace();
// allow virtual thread to terminate
synchronized (lock) {
lock.notifyAll();
}
// check carrier thread's stack trace
assertTrue(contains(carrierStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
assertFalse(contains(carrierStackTrace, "java.lang.Object.wait"));
// check virtual thread's stack trace
assertFalse(contains(vthreadStackTrace, "java.util.concurrent.ForkJoinPool.runWorker"));
assertTrue(contains(vthreadStackTrace, "java.lang.Object.wait"));
}
}
/**
* Test Thread::getStackTrace on parked thread.
* Test Thread::getStackTrace on thread blocked on monitor enter.
*/
@Test
void testGetStackTrace5() throws Exception {
var thread = Thread.ofVirtual().start(LockSupport::park);
await(thread, Thread.State.WAITING);
try {
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.park"));
} finally {
LockSupport.unpark(thread);
thread.join();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetStackTraceBlocked(boolean pinned) throws Exception {
class Foo {
void enter() {
synchronized (this) { }
}
}
}
/**
* Test Thread::getStackTrace on timed-parked thread.
*/
@Test
void testGetStackTrace6() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
LockSupport.parkNanos(Long.MAX_VALUE);
Foo foo = new Foo();
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().unstarted(() -> {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
foo.enter();
});
} else {
ready.set(true);
foo.enter();
}
});
await(thread, Thread.State.TIMED_WAITING);
try {
synchronized (foo) {
thread.start();
awaitTrue(ready);
// wait for thread to block
await(thread, Thread.State.BLOCKED);
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.parkNanos"));
assertTrue(contains(stack, Foo.class.getName() + ".enter"));
}
thread.join();
}
/**
* Test Thread::getStackTrace when thread is waiting in Object.wait.
*/
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetStackTraceWaiting(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait();
});
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to wait
awaitTrue(ready);
await(thread, Thread.State.WAITING);
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "Object.wait"));
} finally {
LockSupport.unpark(thread);
thread.interrupt();
thread.join();
}
}
/**
* Test Thread::getStackTrace on parked thread that is pinned.
* Test Thread::getStackTrace when thread is waiting in timed-Object.wait.
*/
@Test
void testGetStackTrace7() throws Exception {
AtomicBoolean done = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetStackTraceTimedWaiting(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
synchronized (lock) {
try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(Long.MAX_VALUE);
});
} else {
ready.set(true);
lock.wait(Long.MAX_VALUE);
}
} catch (InterruptedException e) { }
}
});
try {
// wait for thread to wait
awaitTrue(ready);
await(thread, Thread.State.TIMED_WAITING);
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "Object.wait"));
} finally {
thread.interrupt();
thread.join();
}
}
/**
* Test Thread::getStackTrace when thread in park.
*/
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetStackTraceParked(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
while (!done.get()) {
LockSupport.park();
}
});
} else {
ready.set(true);
while (!done.get()) {
LockSupport.park();
}
});
}
});
await(thread, Thread.State.WAITING);
try {
// wait for thread to park
awaitTrue(ready);
await(thread, Thread.State.WAITING);
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.park"));
} finally {
@ -2083,20 +2213,33 @@ class ThreadAPI {
}
/**
* Test Thread::getStackTrace on timed-parked thread that is pinned.
* Test Thread::getStackTrace when thread in timed-park.
*/
@Test
void testGetStackTrace8() throws Exception {
AtomicBoolean done = new AtomicBoolean();
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetStackTraceTimedPark(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
if (pinned) {
ready.set(true);
VThreadPinner.runPinned(() -> {
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
} else {
ready.set(true);
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
}
});
await(thread, Thread.State.TIMED_WAITING);
try {
// wait for thread to park
awaitTrue(ready);
await(thread, Thread.State.TIMED_WAITING);
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.parkNanos"));
} finally {
@ -2110,7 +2253,7 @@ class ThreadAPI {
* Test Thread::getStackTrace on terminated thread.
*/
@Test
void testGetStackTrace9() throws Exception {
void testGetStackTraceTerminated() throws Exception {
var thread = Thread.ofVirtual().start(() -> { });
thread.join();
StackTraceElement[] stack = thread.getStackTrace();
@ -2133,7 +2276,7 @@ class ThreadAPI {
*/
@Test
void testGetAllStackTraces2() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
try (ForkJoinPool pool = new ForkJoinPool(1)) {
AtomicReference<Thread> ref = new AtomicReference<>();
Executor scheduler = task -> {
@ -2143,14 +2286,15 @@ class ThreadAPI {
});
};
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread vthread = builder.start(() -> {
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread vthread = factory.newThread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (Exception e) { }
}
});
vthread.start();
// get carrier Thread
Thread carrier;
@ -2168,8 +2312,9 @@ class ThreadAPI {
synchronized (lock) {
lock.notifyAll();
}
vthread.join();
// get stack trace for the carrier thread
// stack trace for the carrier thread
StackTraceElement[] stackTrace = map.get(carrier);
assertNotNull(stackTrace);
assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
@ -2417,6 +2562,15 @@ class ThreadAPI {
}
}
/**
* Waits for the boolean value to become true.
*/
private static void awaitTrue(AtomicBoolean ref) throws Exception {
while (!ref.get()) {
Thread.sleep(20);
}
}
/**
* Waits for the given thread to reach a given state.
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -24,7 +24,7 @@
/**
* @test
* @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws
* @modules java.base/jdk.internal.event
* @modules java.base/java.lang:+open java.base/jdk.internal.event
* @library /test/lib
* @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java
* @run junit/othervm --enable-native-access=ALL-UNNAMED VirtualThreadPinnedEventThrows
@ -36,12 +36,22 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import static org.junit.jupiter.api.Assertions.*;
class VirtualThreadPinnedEventThrows {
@BeforeAll
static void setup() {
// need >=2 carriers for testing pinning when main thread is a virtual thread
if (Thread.currentThread().isVirtual()) {
VThreadRunner.ensureParallelism(2);
}
}
/**
* Test parking when pinned and creating the VirtualThreadPinnedEvent fails with OOME.
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,17 +25,18 @@
* @test
* @summary Stress parking with CompletableFuture timed get
* @requires vm.debug != true & vm.continuations
* @run main/othervm -Xmx1g TimedGet 100000
* @run main/othervm -Xmx1g CompletableFutureTimedGet 100000
*/
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TimedGet {
public class CompletableFutureTimedGet {
static final String RESULT = "foo";
@ -77,30 +78,27 @@ public class TimedGet {
// wait for all threads to terminate
long lastTimestamp = System.currentTimeMillis();
int i = 0;
while (i < threadCount) {
Thread t = threads.get(i);
boolean terminated;
if (t.isAlive()) {
terminated = t.join(Duration.ofMillis(500));
// print trace message so the output tracks progress
long currentTime = System.currentTimeMillis();
if ((currentTime - lastTimestamp) > 500) {
System.out.println(completed.get());
lastTimestamp = currentTime;
boolean done;
do {
done = true;
for (Thread t : threads) {
if (!t.join(Duration.ofSeconds(1))) {
done = false;
}
} else {
terminated = true;
}
if (terminated) {
i++;
// print trace message so the output tracks progress
long currentTime = System.currentTimeMillis();
if (done || ((currentTime - lastTimestamp) > 500)) {
System.out.format("%s => completed %d of %d%n",
Instant.now(), completed.get(), threadCount);
lastTimestamp = currentTime;
}
}
} while (!done);
// all tasks should have completed successfully
int completedCount = completed.get();
System.out.println(completedCount);
if (completedCount != threadCount) {
throw new RuntimeException("completed = " + completedCount);
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Stress test Thread.getStackTrace on virtual threads that are blocking or
* blocked on monitorenter
* @requires vm.debug != true
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm GetStackTraceALotWhenBlocking 500000
*/
/*
* @test
* @requires vm.debug == true & vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm/timeout=300 GetStackTraceALotWhenBlocking 50000
*/
import java.time.Instant;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import jdk.test.lib.thread.VThreadRunner;
public class GetStackTraceALotWhenBlocking {
public static void main(String[] args) throws Exception {
// need at least two carriers
VThreadRunner.ensureParallelism(2);
int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 100_000;
var done = new AtomicBoolean();
var lock = new Object();
Runnable task = () -> {
long count = 0L;
while (!done.get()) {
synchronized (lock) {
pause();
}
count++;
}
System.out.format("%s %s => %d loops%n", Instant.now(), Thread.currentThread(), count);
};
var thread1 = Thread.ofVirtual().start(task);
var thread2 = Thread.ofVirtual().start(task);
try {
for (int i = 1; i <= iterations; i++) {
if ((i % 10_000) == 0) {
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
}
thread1.getStackTrace();
pause();
thread2.getStackTrace();
pause();
}
} finally {
done.set(true);
thread1.join();
thread2.join();
}
}
private static void pause() {
if (ThreadLocalRandom.current().nextBoolean()) {
Thread.onSpinWait();
} else {
Thread.yield();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,30 +23,32 @@
/**
* @test
* @summary Stress test asynchronous Thread.getStackTrace
* @summary Stress test asynchronous Thread.getStackTrace when parking
* @requires vm.debug != true & vm.continuations
* @modules java.base/java.lang:+open
* @compile GetStackTraceALot.java ../ThreadBuilders.java
* @run main GetStackTraceALot
* @library /test/lib
* @run main GetStackTraceALotWhenParking
*/
/**
* @test
* @requires vm.debug == true & vm.continuations
* @modules java.base/java.lang:+open
* @compile GetStackTraceALot.java ../ThreadBuilders.java
* @run main/timeout=300 GetStackTraceALot 1000
* @library /test/lib
* @run main/timeout=300 GetStackTraceALotWhenParking 1000
*/
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
public class GetStackTraceALot {
public class GetStackTraceALotWhenParking {
static class RoundRobinExecutor implements Executor, AutoCloseable {
private final ExecutorService[] executors;
private int next;
@ -83,7 +85,9 @@ public class GetStackTraceALot {
AtomicInteger count = new AtomicInteger();
try (RoundRobinExecutor executor = new RoundRobinExecutor()) {
Thread thread = ThreadBuilders.virtualThreadBuilder(executor).start(() -> {
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(executor);
Thread thread = factory.newThread(() -> {
while (count.incrementAndGet() < ITERATIONS) {
long start = System.nanoTime();
while ((System.nanoTime() - start) < SPIN_NANOS) {
@ -92,6 +96,7 @@ public class GetStackTraceALot {
LockSupport.parkNanos(500_000);
}
});
thread.start();
long start = System.nanoTime();
while (thread.isAlive()) {
@ -99,7 +104,7 @@ public class GetStackTraceALot {
// printStackTrace(stackTrace);
Thread.sleep(5);
if ((System.nanoTime() - start) > 500_000_000) {
System.out.println(count.get());
System.out.format("%s => %d of %d%n", Instant.now(), count.get(), ITERATIONS);
start = System.nanoTime();
}
}

View File

@ -28,7 +28,7 @@
* @requires vm.debug != true
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm GetStackTraceALotWhenPinned 500000
* @run main/othervm --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 500000
*/
/*
@ -36,7 +36,7 @@
* @requires vm.debug == true
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm/timeout=300 GetStackTraceALotWhenPinned 200000
* @run main/othervm/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 200000
*/
import java.time.Instant;
@ -79,7 +79,7 @@ public class GetStackTraceALotWhenPinned {
});
long lastTimestamp = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
for (int i = 1; i <= iterations; i++) {
// wait for virtual thread to arrive
barrier.await();
@ -87,8 +87,8 @@ public class GetStackTraceALotWhenPinned {
LockSupport.unpark(thread);
long currentTime = System.currentTimeMillis();
if ((currentTime - lastTimestamp) > 500) {
System.out.format("%s %d remaining ...%n", Instant.now(), (iterations - i));
if (i == iterations || ((currentTime - lastTimestamp) > 500)) {
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
lastTimestamp = currentTime;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -46,17 +46,17 @@ import jdk.test.lib.thread.VThreadPinner;
public class PinALot {
public static void main(String[] args) throws Exception {
int iterations = 1_000_000;
int iterations;
if (args.length > 0) {
iterations = Integer.parseInt(args[0]);
} else {
iterations = 1_000_000;
}
final int ITERATIONS = iterations;
AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
while (count.incrementAndGet() < ITERATIONS) {
while (count.incrementAndGet() < iterations) {
LockSupport.parkNanos(1);
}
});
@ -65,12 +65,12 @@ public class PinALot {
boolean terminated;
do {
terminated = thread.join(Duration.ofSeconds(1));
System.out.println(Instant.now() + " => " + count.get());
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
} while (!terminated);
int countValue = count.get();
if (countValue != ITERATIONS) {
throw new RuntimeException("count = " + countValue);
if (countValue != iterations) {
throw new RuntimeException("Thread terminated, count=" + countValue);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -36,6 +36,7 @@
*/
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicInteger;
@ -91,11 +92,12 @@ public class PingPong {
boolean terminated;
do {
terminated = t1.join(Duration.ofMillis(500));
if (terminated)
if (terminated) {
terminated = t2.join(Duration.ofMillis(500));
System.out.format("%d %s%n", count1.get(), count2.get());
}
System.out.format("%s => T1 %d of %d, T2 %d of %d%n",
Instant.now(), count1.get(), iterations, count2.get(), iterations);
} while (!terminated);
}
interface Exchanger<E> {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -69,7 +69,7 @@ public class Skynet {
long end = System.currentTimeMillis();
System.out.format("Result: %d in %s ms%n", sum, (end-start));
if (sum != expected)
throw new AssertionError("unexpected result!");
throw new RuntimeException("Expected " + expected);
}
static void skynet(Channel<Long> result, int num, int size, int div) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -41,16 +41,16 @@ import java.util.concurrent.atomic.AtomicInteger;
public class SleepALot {
public static void main(String[] args) throws Exception {
int iterations = 1_000_000;
int iterations;
if (args.length > 0) {
iterations = Integer.parseInt(args[0]);
} else {
iterations = 1_000_000;
}
final int ITERATIONS = iterations;
AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> {
while (count.incrementAndGet() < ITERATIONS) {
while (count.incrementAndGet() < iterations) {
try {
Thread.sleep(Duration.ofNanos(100));
} catch (InterruptedException ignore) { }
@ -60,12 +60,12 @@ public class SleepALot {
boolean terminated;
do {
terminated = thread.join(Duration.ofSeconds(1));
System.out.println(Instant.now() + " => " + count.get());
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
} while (!terminated);
int countValue = count.get();
if (countValue != ITERATIONS) {
throw new RuntimeException("count = " + countValue);
if (countValue != iterations) {
throw new RuntimeException("Thread terminated, count=" + countValue);
}
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test id=timeout
* @summary Stress test timed-Object.wait
* @run main/othervm TimedWaitALot 200
*/
/*
* @test id=timeout-notify
* @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
* at around the same time that the timeout expires.
* @run main/othervm TimedWaitALot 200 true false
*/
/*
* @test id=timeout-interrupt
* @summary Test timed-Object.wait where the waiting thread is awakened with Thread.interrupt
* at around the same time that the timeout expires.
* @run main/othervm TimedWaitALot 200 false true
*/
/*
* @test id=timeout-notify-interrupt
* @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
* and Thread.interrupt at around the same time that the timeout expires.
* @run main/othervm TimedWaitALot 100 true true
*/
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadLocalRandom;
public class TimedWaitALot {
public static void main(String[] args) throws Exception {
int iterations = Integer.parseInt(args[0]);
boolean notify = args.length >= 2 && "true".equals(args[1]);
boolean interrupt = args.length >=3 && "true".equals(args[2]);
// test all timeouts concurrently
int[] timeouts = { 10, 20, 50, 100 };
for (int i = 1; i <= iterations; i++) {
System.out.println(Instant.now() + " => " + i + " of " + iterations);
test(notify, interrupt, timeouts);
}
}
/**
* Start a first virtual thread to wait in Object.wait(millis).
* If {@code notify} is true, start a virtual thread to use Object.notifyAll at around
* the same time that the timeout expires.
* If {@code interrupt} is true, start virtual thread to interrupts the first virtual
* thread at around the same time as the timeout expires.
*/
static void test(boolean notify, boolean interrupt, int... timeouts) throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int timeout : timeouts) {
var queue = new SynchronousQueue<Thread>();
var lock = new Object();
// virtual thread waits with Object.wait(timeout)
executor.submit(() -> {
queue.put(Thread.currentThread());
synchronized (lock) {
lock.wait(timeout);
}
return null;
});
// wait for thread to start
Thread thread = queue.take();
// start thread to Object.notifyAll at around time that the timeout expires
if (notify) {
if (ThreadLocalRandom.current().nextBoolean()) {
synchronized (lock) {
sleepLessThan(timeout);
lock.notifyAll();
}
} else {
sleepLessThan(timeout);
synchronized (lock) {
lock.notifyAll();
}
}
}
// start thread to interrupt first thread at around time that the timeout expires
if (interrupt) {
executor.submit(() -> {
sleepLessThan(timeout);
thread.interrupt();
return null;
});
}
}
}
}
/**
* Sleeps for just less than the given timeout, in millis.
*/
private static void sleepLessThan(long timeout) throws InterruptedException {
int delta = ThreadLocalRandom.current().nextInt(10);
Thread.sleep(timeout - delta);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,7 @@
* @test
* @summary Stress test Thread.yield
* @requires vm.debug != true
* @run main YieldALot 350000
* @run main YieldALot 500000
*/
/*
@ -35,34 +35,35 @@
*/
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
public class YieldALot {
public static void main(String[] args) throws Exception {
int iterations = 1_000_000;
int iterations;
if (args.length > 0) {
iterations = Integer.parseInt(args[0]);
} else {
iterations = 1_000_000;
}
final int ITERATIONS = iterations;
AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> {
while (count.incrementAndGet() < ITERATIONS) {
while (count.incrementAndGet() < iterations) {
Thread.yield();
}
});
boolean terminated;
do {
terminated = thread.join(Duration.ofMillis(500));
System.out.println(count.get());
terminated = thread.join(Duration.ofSeconds(1));
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
} while (!terminated);
int countValue = count.get();
if (countValue != ITERATIONS) {
throw new RuntimeException("count = " + countValue);
if (countValue != iterations) {
throw new RuntimeException("Thread terminated, count=" + countValue);
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test ThreadMXBean.getLockedMonitors returns information about an object
* monitor lock entered with a synchronized native method or JNI MonitorEnter
* @run junit/othervm LockedMonitorInNative
*/
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import static org.junit.jupiter.api.Assertions.*;
public class LockedMonitorInNative {
@BeforeAll
static void setup() throws Exception {
System.loadLibrary("LockedMonitorInNative");
}
/**
* Test ThreadMXBean.getLockedMonitors returns information about an object
* monitor lock entered with a synchronized native method.
*/
@Test
void testSynchronizedNative() {
Object lock = this;
runWithSynchronizedNative(() -> {
assertTrue(holdsLock(lock), "Thread does not hold lock");
});
}
/**
* Test ThreadMXBean.getLockedMonitors returns information about an object
* monitor lock entered with JNI MonitorEnter.
*/
@Test
void testMonitorEnteredInNative() {
var lock = new Object();
runWithMonitorEnteredInNative(lock, () -> {
assertTrue(holdsLock(lock), "Thread does not hold lock");
});
}
private boolean holdsLock(Object lock) {
int hc = System.identityHashCode(lock);
long tid = Thread.currentThread().threadId();
ThreadInfo ti = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
.getThreadInfo(new long[] { tid }, true, true)[0];
return Arrays.stream(ti.getLockedMonitors())
.anyMatch(mi -> mi.getIdentityHashCode() == hc);
}
/**
* Invokes the given task's run method while holding the monitor for "this".
*/
private synchronized native void runWithSynchronizedNative(Runnable task);
/**
* Invokes the given task's run method while holding the monitor for the given
* object. The monitor is entered with JNI MonitorEnter, and exited with JNI MonitorExit.
*/
private native void runWithMonitorEnteredInNative(Object lock, Runnable task);
/**
* Called from native methods to run the given task.
*/
private void run(Runnable task) {
task.run();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -41,19 +41,20 @@
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.channels.Selector;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors;
import jdk.test.lib.thread.VThreadPinner;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ -195,7 +196,7 @@ public class VirtualThreads {
*/
@Test
void testGetThreadInfoCarrierThread() throws Exception {
assumeTrue(supportsCustomScheduler(), "No support for custom schedulers");
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
try (ExecutorService pool = Executors.newFixedThreadPool(1)) {
var carrierRef = new AtomicReference<Thread>();
Executor scheduler = (task) -> {
@ -204,19 +205,30 @@ public class VirtualThreads {
task.run();
});
};
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
// start virtual thread so carrier Thread can be captured
virtualThreadBuilder(scheduler).start(() -> { }).join();
Thread thread = factory.newThread(() -> { });
thread.start();
thread.join();
Thread carrier = carrierRef.get();
assertTrue(carrier != null && !carrier.isVirtual());
try (Selector sel = Selector.open()) {
// start virtual thread that blocks in a native method
virtualThreadBuilder(scheduler).start(() -> {
String selClassName = sel.getClass().getName();
// start virtual thread that blocks while pinned
Thread vthread = factory.newThread(() -> {
try {
sel.select();
VThreadPinner.runPinned(sel::select);
} catch (Exception e) { }
});
vthread.start();
// wait for virtual thread to block in select
while (!contains(vthread.getStackTrace(), selClassName)) {
Thread.sleep(20);
}
// invoke getThreadInfo get and check the stack trace
long tid = carrier.getId();
@ -225,7 +237,7 @@ public class VirtualThreads {
// should only see carrier frames
StackTraceElement[] stack = info.getStackTrace();
assertTrue(contains(stack, "java.util.concurrent.ThreadPoolExecutor"));
assertFalse(contains(stack, "java.nio.channels.Selector"));
assertFalse(contains(stack, selClassName));
// carrier should not be holding any monitors
assertEquals(0, info.getLockedMonitors().length);
@ -351,40 +363,4 @@ public class VirtualThreads {
.map(StackTraceElement::getClassName)
.anyMatch(className::equals);
}
/**
* Returns a builder to create virtual threads that use the given scheduler.
* @throws UnsupportedOperationException if there is no support for custom schedulers
*/
private static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
Thread.Builder.OfVirtual builder = Thread.ofVirtual();
try {
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
ctor.setAccessible(true);
return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException re) {
throw re;
}
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Return true if custom schedulers are supported.
*/
private static boolean supportsCustomScheduler() {
try (var pool = Executors.newCachedThreadPool()) {
try {
virtualThreadBuilder(pool);
return true;
} catch (UnsupportedOperationException e) {
return false;
}
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
JNIEXPORT void JNICALL
Java_LockedMonitorInNative_runWithSynchronizedNative(JNIEnv *env, jobject obj, jobject task) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
if (mid != NULL) {
(*env)->CallVoidMethod(env, obj, mid, task);
}
}
JNIEXPORT void JNICALL
Java_LockedMonitorInNative_runWithMonitorEnteredInNative(JNIEnv *env, jobject obj, jobject lock, jobject task) {
jclass clazz = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
if (mid != NULL && (*env)->MonitorEnter(env, lock) == 0) {
(*env)->CallVoidMethod(env, obj, mid, task);
(*env)->MonitorExit(env, lock); // can be called with pending exception
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -426,7 +426,7 @@ class ThreadFlockTest {
long startMillis = millisTime();
boolean done = flock.awaitAll(Duration.ofSeconds(30));
assertTrue(done);
checkDuration(startMillis, 1900, 4000);
checkDuration(startMillis, 1900, 20_000);
}
}
@ -453,7 +453,7 @@ class ThreadFlockTest {
flock.awaitAll(Duration.ofSeconds(2));
fail("awaitAll did not throw");
} catch (TimeoutException e) {
checkDuration(startMillis, 1900, 4000);
checkDuration(startMillis, 1900, 20_000);
}
} finally {
latch.countDown();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,28 +21,50 @@
* questions.
*/
package jdk.test.lib.thread;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* Helper class for creating Thread buidlers.
* Helper class to allow tests run virtual threads with a custom scheduler.
*
* Tests using this class need to open java.base/java.lang.
*/
class ThreadBuilders {
private ThreadBuilders() { }
public class VThreadScheduler {
private VThreadScheduler() { }
private static final Constructor<?> VTBUILDER_CTOR;
static {
/**
* Returns the scheduler for the given virtual thread.
*/
public static Executor scheduler(Thread thread) {
if (!thread.isVirtual())
throw new IllegalArgumentException("Not a virtual thread");
try {
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
ctor.setAccessible(true);
VTBUILDER_CTOR = ctor;
Field scheduler = Class.forName("java.lang.VirtualThread")
.getDeclaredField("scheduler");
scheduler.setAccessible(true);
return (Executor) scheduler.get(thread);
} catch (Exception e) {
throw new InternalError(e);
throw new RuntimeException(e);
}
}
/**
* Return true if custom schedulers are supported.
*/
public static boolean supportsCustomScheduler() {
try (var pool = Executors.newCachedThreadPool()) {
try {
virtualThreadBuilder(pool);
return true;
} catch (UnsupportedOperationException e) {
return false;
}
}
}
@ -50,9 +72,12 @@ class ThreadBuilders {
* Returns a builder to create virtual threads that use the given scheduler.
* @throws UnsupportedOperationException if custom schedulers are not supported
*/
static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
public static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
try {
return (Thread.Builder.OfVirtual) VTBUILDER_CTOR.newInstance(scheduler);
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
ctor.setAccessible(true);
return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException re) {
@ -65,16 +90,10 @@ class ThreadBuilders {
}
/**
* Return true if custom schedulers are supported.
* Returns a ThreadFactory to create virtual threads that use the given scheduler.
* @throws UnsupportedOperationException if custom schedulers are not supported
*/
static boolean supportsCustomScheduler() {
try (var pool = Executors.newCachedThreadPool()) {
try {
virtualThreadBuilder(pool);
return true;
} catch (UnsupportedOperationException e) {
return false;
}
}
public static ThreadFactory virtualThreadFactory(Executor scheduler) {
return virtualThreadBuilder(scheduler).factory();
}
}