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_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_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_libnativeStack := java.base:libjvm
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
else else
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libsystemclssearch_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_libCompleteExit += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -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_LIBS_exeGetCreatedJavaVMs := -lpthread
BUILD_HOTSPOT_JTREG_EXECUTABLES_JDK_LIBS_exeGetCreatedJavaVMs := java.base:libjvm 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.ResourceBundle;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -2264,6 +2263,7 @@ public final class System {
super(fd); super(fd);
} }
@Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
boolean attempted = Blocker.begin(); boolean attempted = Blocker.begin();
try { try {
@ -2677,14 +2677,6 @@ public final class System {
return Thread.currentCarrierThread(); 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) { public <T> T getCarrierThreadLocal(CarrierThreadLocal<T> local) {
return ((ThreadLocal<T>)local).getCarrierThreadLocal(); 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.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import jdk.internal.event.VirtualThreadEndEvent; import jdk.internal.event.VirtualThreadEndEvent;
import jdk.internal.event.VirtualThreadPinnedEvent; import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.internal.event.VirtualThreadStartEvent; import jdk.internal.event.VirtualThreadStartEvent;
@ -62,14 +63,13 @@ import sun.security.action.GetPropertyAction;
import static java.util.concurrent.TimeUnit.*; import static java.util.concurrent.TimeUnit.*;
/** /**
* A thread that is scheduled by the Java virtual machine rather than the operating * A thread that is scheduled by the Java virtual machine rather than the operating system.
* system.
*/ */
final class VirtualThread extends BaseVirtualThread { final class VirtualThread extends BaseVirtualThread {
private static final Unsafe U = Unsafe.getUnsafe(); private static final Unsafe U = Unsafe.getUnsafe();
private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads"); private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler(); 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 int TRACE_PINNING_MODE = tracePinningMode();
private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state"); 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 * on the current thread before the task runs or continues. It unmounts when the
* task completes or yields. * task completes or yields.
*/ */
@ChangesCurrentThread @ChangesCurrentThread // allow mount/unmount to be inlined
private void runContinuation() { private void runContinuation() {
// the carrier must be a platform thread // the carrier must be a platform thread
if (Thread.currentThread().isVirtual()) { if (Thread.currentThread().isVirtual()) {
@ -257,42 +257,109 @@ final class VirtualThread extends BaseVirtualThread {
* Submits the runContinuation task to the scheduler. For the default scheduler, * 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, * 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. * 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 * @throws RejectedExecutionException
*/ */
private void submitRunContinuation() { @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 { try {
scheduler.execute(runContinuation); scheduler.execute(runContinuation);
} finally {
switchToVirtualThread(vthread);
}
} else {
scheduler.execute(runContinuation);
}
done = true;
} catch (RejectedExecutionException ree) { } catch (RejectedExecutionException ree) {
submitFailed(ree); submitFailed(ree);
throw 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. * 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 * @throws RejectedExecutionException
* @see ForkJoinPool#lazySubmit(ForkJoinTask) * @see ForkJoinPool#lazySubmit(ForkJoinTask)
*/ */
private void lazySubmitRunContinuation(ForkJoinPool pool) { private void lazySubmitRunContinuation(ForkJoinPool pool) {
assert Thread.currentThread() instanceof CarrierThread;
try { try {
pool.lazySubmit(ForkJoinTask.adapt(runContinuation)); pool.lazySubmit(ForkJoinTask.adapt(runContinuation));
} catch (RejectedExecutionException ree) { } catch (RejectedExecutionException ree) {
submitFailed(ree); submitFailed(ree);
throw ree; throw ree;
} catch (OutOfMemoryError e) {
submitRunContinuation(pool, true);
} }
} }
/** /**
* Submits the runContinuation task to the given scheduler as an external submit. * 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 * @throws RejectedExecutionException
* @see ForkJoinPool#externalSubmit(ForkJoinTask) * @see ForkJoinPool#externalSubmit(ForkJoinTask)
*/ */
private void externalSubmitRunContinuation(ForkJoinPool pool) { private void externalSubmitRunContinuation(ForkJoinPool pool) {
assert Thread.currentThread() instanceof CarrierThread;
try { try {
pool.externalSubmit(ForkJoinTask.adapt(runContinuation)); pool.externalSubmit(ForkJoinTask.adapt(runContinuation));
} catch (RejectedExecutionException ree) { } catch (RejectedExecutionException ree) {
submitFailed(ree); submitFailed(ree);
throw 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 @ChangesCurrentThread
@ReservedStackAccess @ReservedStackAccess
private void unmount() { private void unmount() {
assert !Thread.holdsLock(interruptLock);
// set Thread.currentThread() to return the platform thread // set Thread.currentThread() to return the platform thread
Thread carrier = this.carrierThread; Thread carrier = this.carrierThread;
carrier.setCurrentThread(carrier); carrier.setCurrentThread(carrier);
@ -417,7 +486,7 @@ final class VirtualThread extends BaseVirtualThread {
*/ */
@ChangesCurrentThread @ChangesCurrentThread
@JvmtiMountTransition @JvmtiMountTransition
private void switchToVirtualThread(VirtualThread vthread) { private static void switchToVirtualThread(VirtualThread vthread) {
Thread carrier = vthread.carrierThread; Thread carrier = vthread.carrierThread;
assert carrier == Thread.currentCarrierThread(); assert carrier == Thread.currentCarrierThread();
carrier.setCurrentThread(vthread); carrier.setCurrentThread(vthread);
@ -474,13 +543,12 @@ final class VirtualThread extends BaseVirtualThread {
// may have been unparked while parking // may have been unparked while parking
if (parkPermit && compareAndSetState(newState, UNPARKED)) { if (parkPermit && compareAndSetState(newState, UNPARKED)) {
// lazy submit to continue on the current thread as carrier if possible // lazy submit to continue on the current carrier if possible
if (currentThread() instanceof CarrierThread ct) { if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
lazySubmitRunContinuation(ct.getPool()); lazySubmitRunContinuation(ct.getPool());
} else { } else {
submitRunContinuation(); submitRunContinuation();
} }
} }
return; return;
} }
@ -561,8 +629,8 @@ final class VirtualThread extends BaseVirtualThread {
// scoped values may be inherited // scoped values may be inherited
inheritScopedValueBindings(container); inheritScopedValueBindings(container);
// submit task to run thread // submit task to run thread, using externalSubmit if possible
submitRunContinuation(); externalSubmitRunContinuationOrThrow();
started = true; started = true;
} finally { } finally {
if (!started) { if (!started) {
@ -707,7 +775,7 @@ final class VirtualThread extends BaseVirtualThread {
// need to switch to current carrier thread to avoid nested parking // need to switch to current carrier thread to avoid nested parking
switchToCarrierThread(); switchToCarrierThread();
try { try {
return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS); return schedule(this::unpark, nanos, NANOSECONDS);
} finally { } finally {
switchToVirtualThread(this); switchToVirtualThread(this);
} }
@ -718,6 +786,7 @@ final class VirtualThread extends BaseVirtualThread {
*/ */
@ChangesCurrentThread @ChangesCurrentThread
private void cancel(Future<?> future) { private void cancel(Future<?> future) {
assert Thread.currentThread() == this;
if (!future.isDone()) { if (!future.isDone()) {
// need to switch to current carrier thread to avoid nested parking // need to switch to current carrier thread to avoid nested parking
switchToCarrierThread(); switchToCarrierThread();
@ -730,33 +799,26 @@ final class VirtualThread extends BaseVirtualThread {
} }
/** /**
* Re-enables this virtual thread for scheduling. If the virtual thread was * Re-enables this virtual thread for scheduling. If this virtual thread is parked
* {@link #park() parked} then it will be unblocked, otherwise its next call * then its task is scheduled to continue, otherwise its next call to {@code park} or
* to {@code park} or {@linkplain #parkNanos(long) parkNanos} is guaranteed * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block.
* not to block.
* @throws RejectedExecutionException if the scheduler cannot accept a task * @throws RejectedExecutionException if the scheduler cannot accept a task
*/ */
@Override @Override
@ChangesCurrentThread
void unpark() { void unpark() {
Thread currentThread = Thread.currentThread(); if (!getAndSetParkPermit(true) && currentThread() != this) {
if (!getAndSetParkPermit(true) && currentThread != this) {
int s = state(); int s = state();
boolean parked = (s == PARKED) || (s == TIMED_PARKED);
if (parked && compareAndSetState(s, UNPARKED)) { // unparked while parked
if (currentThread instanceof VirtualThread vthread) { if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) {
vthread.switchToCarrierThread();
try {
submitRunContinuation(); submitRunContinuation();
} finally { return;
switchToVirtualThread(vthread);
} }
} else {
submitRunContinuation(); // unparked while parked when pinned
} if (s == PINNED || s == TIMED_PINNED) {
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
// unpark carrier thread when pinned // unpark carrier thread when pinned
notifyJvmtiDisableSuspend(true); disableSuspendAndPreempt();
try { try {
synchronized (carrierThreadAccessLock()) { synchronized (carrierThreadAccessLock()) {
Thread carrier = carrierThread; Thread carrier = carrierThread;
@ -765,8 +827,9 @@ final class VirtualThread extends BaseVirtualThread {
} }
} }
} finally { } finally {
notifyJvmtiDisableSuspend(false); enableSuspendAndPreempt();
} }
return;
} }
} }
} }
@ -859,11 +922,11 @@ final class VirtualThread extends BaseVirtualThread {
@Override @Override
void blockedOn(Interruptible b) { void blockedOn(Interruptible b) {
notifyJvmtiDisableSuspend(true); disableSuspendAndPreempt();
try { try {
super.blockedOn(b); super.blockedOn(b);
} finally { } finally {
notifyJvmtiDisableSuspend(false); enableSuspendAndPreempt();
} }
} }
@ -874,9 +937,9 @@ final class VirtualThread extends BaseVirtualThread {
checkAccess(); checkAccess();
// if current thread is a virtual thread then prevent it from being // 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; Interruptible blocker;
notifyJvmtiDisableSuspend(true); disableSuspendAndPreempt();
try { try {
synchronized (interruptLock) { synchronized (interruptLock) {
interrupted = true; interrupted = true;
@ -890,18 +953,22 @@ final class VirtualThread extends BaseVirtualThread {
if (carrier != null) carrier.setInterrupt(); if (carrier != null) carrier.setInterrupt();
} }
} finally { } finally {
notifyJvmtiDisableSuspend(false); enableSuspendAndPreempt();
} }
// notify blocker after releasing interruptLock // notify blocker after releasing interruptLock
if (blocker != null) { if (blocker != null) {
blocker.postInterrupt(); blocker.postInterrupt();
} }
// make available parking permit, unpark thread if parked
unpark();
} else { } else {
interrupted = true; interrupted = true;
carrierThread.setInterrupt(); carrierThread.setInterrupt();
setParkPermit(true);
} }
unpark();
} }
@Override @Override
@ -914,14 +981,14 @@ final class VirtualThread extends BaseVirtualThread {
assert Thread.currentThread() == this; assert Thread.currentThread() == this;
boolean oldValue = interrupted; boolean oldValue = interrupted;
if (oldValue) { if (oldValue) {
notifyJvmtiDisableSuspend(true); disableSuspendAndPreempt();
try { try {
synchronized (interruptLock) { synchronized (interruptLock) {
interrupted = false; interrupted = false;
carrierThread.clearInterrupt(); carrierThread.clearInterrupt();
} }
} finally { } finally {
notifyJvmtiDisableSuspend(false); enableSuspendAndPreempt();
} }
} }
return oldValue; return oldValue;
@ -946,7 +1013,8 @@ final class VirtualThread extends BaseVirtualThread {
return Thread.State.RUNNABLE; return Thread.State.RUNNABLE;
case RUNNING: case RUNNING:
// if mounted then return state of carrier thread // if mounted then return state of carrier thread
notifyJvmtiDisableSuspend(true); if (Thread.currentThread() != this) {
disableSuspendAndPreempt();
try { try {
synchronized (carrierThreadAccessLock()) { synchronized (carrierThreadAccessLock()) {
Thread carrierThread = this.carrierThread; Thread carrierThread = this.carrierThread;
@ -955,7 +1023,8 @@ final class VirtualThread extends BaseVirtualThread {
} }
} }
} finally { } finally {
notifyJvmtiDisableSuspend(false); enableSuspendAndPreempt();
}
} }
// runnable, mounted // runnable, mounted
return Thread.State.RUNNABLE; return Thread.State.RUNNABLE;
@ -1068,31 +1137,48 @@ final class VirtualThread extends BaseVirtualThread {
sb.append(name); sb.append(name);
} }
sb.append("]/"); sb.append("]/");
Thread carrier = carrierThread;
if (carrier != null) { // add the carrier state and thread name when mounted
// include the carrier thread state and name when mounted boolean mounted;
notifyJvmtiDisableSuspend(true); if (Thread.currentThread() == this) {
mounted = appendCarrierInfo(sb);
} else {
disableSuspendAndPreempt();
try { try {
synchronized (carrierThreadAccessLock()) { synchronized (carrierThreadAccessLock()) {
carrier = carrierThread; mounted = appendCarrierInfo(sb);
}
} finally {
enableSuspendAndPreempt();
}
}
// 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) { if (carrier != null) {
String stateAsString = carrier.threadState().toString(); String stateAsString = carrier.threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT)); sb.append(stateAsString.toLowerCase(Locale.ROOT));
sb.append('@'); sb.append('@');
sb.append(carrier.getName()); sb.append(carrier.getName());
return true;
} else {
return false;
} }
} }
} finally {
notifyJvmtiDisableSuspend(false);
}
}
// include virtual thread state when not mounted
if (carrier == null) {
String stateAsString = threadState().toString();
sb.append(stateAsString.toLowerCase(Locale.ROOT));
}
return sb.toString();
}
@Override @Override
public int hashCode() { public int hashCode() {
@ -1127,6 +1213,22 @@ final class VirtualThread extends BaseVirtualThread {
return interruptLock; 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 -- // -- wrappers for get/set of state, parking permit, and carrier thread --
private int state() { private int state() {
@ -1188,10 +1290,16 @@ final class VirtualThread extends BaseVirtualThread {
private static native void registerNatives(); private static native void registerNatives();
static { static {
registerNatives(); 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") @SuppressWarnings("removal")
private static ForkJoinPool createDefaultScheduler() { 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() { private static Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
String propValue = GetPropertyAction.privilegedGetProperty("jdk.unparker.maxPoolSize"); long tid = Thread.currentThread().threadId();
int poolSize; int index = (int) tid & (DELAYED_TASK_SCHEDULERS.length - 1);
if (propValue != null) { return DELAYED_TASK_SCHEDULERS[index].schedule(command, delay, unit);
poolSize = Integer.parseInt(propValue);
} else {
poolSize = 1;
} }
/**
* 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) {
queueCount = Integer.parseInt(propValue);
if (queueCount != Integer.highestOneBit(queueCount)) {
throw new RuntimeException("Value of " + propName + " must be power of 2");
}
} else {
int ncpus = Runtime.getRuntime().availableProcessors();
queueCount = Math.max(Integer.highestOneBit(ncpus / 4), 1);
}
var schedulers = new ScheduledExecutorService[queueCount];
for (int i = 0; i < queueCount; i++) {
ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor) ScheduledThreadPoolExecutor stpe = (ScheduledThreadPoolExecutor)
Executors.newScheduledThreadPool(poolSize, task -> { Executors.newScheduledThreadPool(1, task -> {
return InnocuousThread.newThread("VirtualThread-unparker", task); Thread t = InnocuousThread.newThread("VirtualThread-unparker", task);
t.setDaemon(true);
return t;
}); });
stpe.setRemoveOnCancelPolicy(true); stpe.setRemoveOnCancelPolicy(true);
return stpe; schedulers[i] = stpe;
}
return schedulers;
} }
/** /**

View File

@ -35,7 +35,9 @@
package java.util.concurrent.locks; 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; import jdk.internal.misc.Unsafe;
/** /**
@ -176,7 +178,7 @@ public class LockSupport {
public static void unpark(Thread thread) { public static void unpark(Thread thread) {
if (thread != null) { if (thread != null) {
if (thread.isVirtual()) { if (thread.isVirtual()) {
VirtualThreads.unpark(thread); JLA.unparkVirtualThread(thread);
} else { } else {
U.unpark(thread); U.unpark(thread);
} }
@ -216,7 +218,7 @@ public class LockSupport {
setBlocker(t, blocker); setBlocker(t, blocker);
try { try {
if (t.isVirtual()) { if (t.isVirtual()) {
VirtualThreads.park(); JLA.parkVirtualThread();
} else { } else {
U.park(false, 0L); U.park(false, 0L);
} }
@ -264,7 +266,7 @@ public class LockSupport {
setBlocker(t, blocker); setBlocker(t, blocker);
try { try {
if (t.isVirtual()) { if (t.isVirtual()) {
VirtualThreads.park(nanos); JLA.parkVirtualThread(nanos);
} else { } else {
U.park(false, nanos); U.park(false, nanos);
} }
@ -311,11 +313,7 @@ public class LockSupport {
Thread t = Thread.currentThread(); Thread t = Thread.currentThread();
setBlocker(t, blocker); setBlocker(t, blocker);
try { try {
if (t.isVirtual()) { parkUntil(deadline);
VirtualThreads.parkUntil(deadline);
} else {
U.park(true, deadline);
}
} finally { } finally {
setBlocker(t, null); setBlocker(t, null);
} }
@ -366,7 +364,7 @@ public class LockSupport {
*/ */
public static void park() { public static void park() {
if (Thread.currentThread().isVirtual()) { if (Thread.currentThread().isVirtual()) {
VirtualThreads.park(); JLA.parkVirtualThread();
} else { } else {
U.park(false, 0L); U.park(false, 0L);
} }
@ -405,7 +403,7 @@ public class LockSupport {
public static void parkNanos(long nanos) { public static void parkNanos(long nanos) {
if (nanos > 0) { if (nanos > 0) {
if (Thread.currentThread().isVirtual()) { if (Thread.currentThread().isVirtual()) {
VirtualThreads.park(nanos); JLA.parkVirtualThread(nanos);
} else { } else {
U.park(false, nanos); U.park(false, nanos);
} }
@ -444,7 +442,8 @@ public class LockSupport {
*/ */
public static void parkUntil(long deadline) { public static void parkUntil(long deadline) {
if (Thread.currentThread().isVirtual()) { if (Thread.currentThread().isVirtual()) {
VirtualThreads.parkUntil(deadline); long millis = deadline - System.currentTimeMillis();
JLA.parkVirtualThread(TimeUnit.MILLISECONDS.toNanos(millis));
} else { } else {
U.park(true, deadline); U.park(true, deadline);
} }
@ -462,4 +461,5 @@ public class LockSupport {
private static final long PARKBLOCKER private static final long PARKBLOCKER
= U.objectFieldOffset(Thread.class, "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.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -504,11 +503,6 @@ public interface JavaLangAccess {
*/ */
Thread currentCarrierThread(); 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. * 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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. // this is too fragile, implementation can change at any time.
checkFrames(vThread1, false, 14); checkFrames(vThread1, false, 13);
LockSupport.unpark(vThread1); LockSupport.unpark(vThread1);
vThread1.join(); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,24 +25,29 @@
* @test id=default * @test id=default
* @bug 8312498 * @bug 8312498
* @summary Basic test for JVMTI GetThreadState with virtual threads * @summary Basic test for JVMTI GetThreadState with virtual threads
* @modules java.base/java.lang:+open
* @library /test/lib * @library /test/lib
* @run junit/othervm/native GetThreadStateTest * @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest
*/ */
/* /*
* @test id=no-vmcontinuations * @test id=no-vmcontinuations
* @requires vm.continuations * @requires vm.continuations
* @modules java.base/java.lang:+open
* @library /test/lib * @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.StringJoiner;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner; import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; 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.*; import static org.junit.jupiter.api.Assertions.*;
class GetThreadStateTest { class GetThreadStateTest {
@ -51,6 +56,11 @@ class GetThreadStateTest {
static void setup() { static void setup() {
System.loadLibrary("GetThreadStateTest"); System.loadLibrary("GetThreadStateTest");
init(); 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 @ParameterizedTest
void testMonitorEnter() throws Exception { @ValueSource(booleans = { true, false })
var started = new AtomicBoolean(); void testMonitorEnter(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
Object lock = new Object(); Object lock = new Object();
var thread = Thread.ofVirtual().unstarted(() -> { var thread = Thread.ofVirtual().unstarted(() -> {
started.set(true); if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
synchronized (lock) { } synchronized (lock) { }
}); });
} else {
ready.set(true);
synchronized (lock) { }
}
});
try { try {
synchronized (lock) { synchronized (lock) {
// start thread and wait for it to start execution // start thread and wait for it to start execution
thread.start(); thread.start();
awaitTrue(started); awaitTrue(ready);
// thread should block on monitor enter // thread should block on monitor enter
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_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 @ParameterizedTest
void testObjectWait() throws Exception { @ValueSource(booleans = { true, false })
var started = new AtomicBoolean(); void testObjectWait(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
Object lock = new Object(); Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
started.set(true);
try { try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(); lock.wait();
});
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
}); });
try { try {
// wait for thread to start execution // wait for thread to start execution
awaitTrue(started); awaitTrue(ready);
// thread should wait // thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE | 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 @ParameterizedTest
void testObjectWaitMillis() throws Exception { @ValueSource(booleans = { true, false })
var started = new AtomicBoolean(); void testObjectWaitMillis(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
Object lock = new Object(); Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
started.set(true); synchronized (lock) {
try { try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(Long.MAX_VALUE); lock.wait(Long.MAX_VALUE);
});
} else {
ready.set(true);
lock.wait(Long.MAX_VALUE);
}
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
}
}); });
try { try {
// wait for thread to start execution // wait for thread to start execution
awaitTrue(started); awaitTrue(ready);
// thread should wait // thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -219,21 +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 @ParameterizedTest
void testPark() throws Exception { @ValueSource(booleans = { true, false })
var started = new AtomicBoolean(); void testPark(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
started.set(true); if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.park(); LockSupport.park();
} }
}); });
} else {
ready.set(true);
while (!done.get()) {
LockSupport.park();
}
}
});
try { try {
// wait for thread to start execution // wait for thread to start execution
awaitTrue(started); awaitTrue(ready);
// thread should park // thread should park
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -249,85 +295,31 @@ class GetThreadStateTest {
} }
/** /**
* Test state of thread parked with LockSupport.parkNanos. * Test state of thread parked with LockSupport.parkNanos when pinned and not pinned.
*/ */
@Test @ParameterizedTest
void testParkNanos() throws Exception { @ValueSource(booleans = { true, false })
var started = new AtomicBoolean(); void testParkNanos(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
started.set(true); if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE); LockSupport.parkNanos(Long.MAX_VALUE);
} }
}); });
try { } else {
// wait for thread to start execution ready.set(true);
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);
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 while holding a monitor.
*/
@Test
void testParkNanosWhenPinned() throws Exception {
var started = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
started.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE); LockSupport.parkNanos(Long.MAX_VALUE);
} }
}); }
}); });
try { try {
// wait for thread to start execution // wait for thread to start execution
awaitTrue(started); awaitTrue(ready);
// thread should park // thread should park
int expected = JVMTI_THREAD_STATE_ALIVE | 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -28,47 +28,22 @@
* @requires vm.continuations * @requires vm.continuations
* @requires vm.jvmti * @requires vm.jvmti
* @requires vm.compMode != "Xcomp" * @requires vm.compMode != "Xcomp"
* @modules java.base/java.lang:+open
* @library /test/lib
* @run main/othervm/native * @run main/othervm/native
* -Djdk.virtualThreadScheduler.parallelism=9
* -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach * -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach
*/ */
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachine;
import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import jdk.test.lib.thread.VThreadRunner;
/*
* 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);
}
}
}
public class VThreadEventTest { public class VThreadEventTest {
static final int TCNT1 = 10; static final int PARKED_THREAD_COUNT = 4;
static final int TCNT2 = 4; static final int SPINNING_THREAD_COUNT = 4;
static final int TCNT3 = 4;
static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3;
private static void log(String msg) { System.out.println(msg); } private static void log(String msg) { System.out.println(msg); }
@ -77,128 +52,96 @@ public class VThreadEventTest {
private static native int threadUnmountCount(); private static native int threadUnmountCount();
private static volatile boolean attached; 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); // called by agent when it is initialized and has enabled events
private static CountDownLatch ready1 = new CountDownLatch(TCNT1); static void agentStarted() {
private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT); attached = true;
private static CountDownLatch mready = new CountDownLatch(1);
private static void await(CountDownLatch dumpedLatch) {
try {
dumpedLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} }
}
// 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 { public static void main(String[] args) throws Exception {
if (Runtime.getRuntime().availableProcessors() < 8) { if (Thread.currentThread().isVirtual()) {
log("WARNING: test expects at least 8 processors."); System.out.println("Skipping test as current thread is a virtual thread");
return;
} }
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) { VThreadRunner.ensureParallelism(SPINNING_THREAD_COUNT+1);
for (int i = 0; i < TCNT1; i++) {
executorService.execute(test1); // start threads that park (unmount)
} var threads1 = new ArrayList<Thread>();
for (int i = 0; i < TCNT2; i++) { for (int i = 0; i < PARKED_THREAD_COUNT; i++) {
executorService.execute(test2); var started = new AtomicBoolean();
} var thread = Thread.startVirtualThread(() -> {
for (int i = 0; i < TCNT3; i++) { started.set(true);
executorService.execute(test3); LockSupport.park();
} });
await(ready0);
mready.countDown(); // wait for thread to start execution + park
await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1() while (!started.get()) {
// 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); 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();
}
});
// wait for thread to start execution
while (!started.get()) {
Thread.sleep(10);
}
threads2.add(thread);
}
// attach to the current VM
VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid())); VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid()));
vm.loadAgentLibrary("VThreadEventTest"); vm.loadAgentLibrary("VThreadEventTest");
Thread.sleep(200); // to allow the agent to get ready
attached = true; // wait for agent to start
for (Thread t : test1Threads) { while (!attached) {
t.interrupt(); Thread.sleep(10);
} }
ready2.await();
// unpark the threads that were parked
for (Thread thread : threads1) {
LockSupport.unpark(thread);
} }
// wait until all VirtualThreadEnd events have been sent
for (int sleepNo = 1; threadEndCount() < THREAD_CNT; sleepNo++) { // wait for all threads to terminate
Thread.sleep(100); for (Thread thread : threads1) {
if (sleepNo % 100 == 0) { // 10 sec period of waiting thread.join();
log("main: waited seconds: " + sleepNo/10);
} }
for (Thread thread : threads2) {
thread.join();
} }
int threadEndCnt = threadEndCount(); int threadEndCnt = threadEndCount();
int threadMountCnt = threadMountCount(); int threadMountCnt = threadMountCount();
int threadUnmountCnt = threadUnmountCount(); int threadUnmountCnt = threadUnmountCount();
int threadEndExp = THREAD_CNT;
int threadMountExp = THREAD_CNT - TCNT2;
int threadUnmountExp = THREAD_CNT + TCNT3;
log("ThreadEnd cnt: " + threadEndCnt + " (expected: " + threadEndExp + ")"); int threadCount = PARKED_THREAD_COUNT + SPINNING_THREAD_COUNT;
log("ThreadMount cnt: " + threadMountCnt + " (expected: " + threadMountExp + ")"); log("VirtualThreadEnd events: " + threadEndCnt + ", expected: " + threadCount);
log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")"); log("VirtualThreadMount events: " + threadMountCnt + ", expected: " + PARKED_THREAD_COUNT);
log("VirtualThreadUnmount events: " + threadUnmountCnt + ", expected: " + threadCount);
if (threadEndCnt != threadEndExp) { boolean failed = false;
log("FAILED: unexpected count of ThreadEnd events"); if (threadEndCnt != threadCount) {
log("FAILED: unexpected count of VirtualThreadEnd events");
failed = true; failed = true;
} }
if (threadMountCnt != threadMountExp) { if (threadMountCnt != PARKED_THREAD_COUNT) {
log("FAILED: unexpected count of ThreadMount events"); log("FAILED: unexpected count of VirtualThreadMount events");
failed = true; failed = true;
} }
if (threadUnmountCnt != threadUnmountExp) { if (threadUnmountCnt != threadCount) {
log("FAILED: unexpected count of ThreadUnmount events"); log("FAILED: unexpected count of VirtualThreadUnmount events");
failed = true; failed = true;
} }
if (failed) { 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; jvmtiEventCallbacks callbacks;
jvmtiCapabilities caps; jvmtiCapabilities caps;
jvmtiError err; jvmtiError err;
JNIEnv *env;
jsize nVMs;
jint res;
jclass clazz;
jmethodID mid;
LOG("Agent_OnAttach started\n"); LOG("Agent_OnAttach started\n");
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { 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"); check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadUnmount");
LOG("vthread events enabled\n"); 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; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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 * @summary Test virtual threads using a custom scheduler
* @requires vm.continuations * @requires vm.continuations
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib
* @run junit CustomScheduler * @run junit CustomScheduler
*/ */
@ -35,9 +36,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; 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.Test;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
@ -65,10 +69,13 @@ class CustomScheduler {
*/ */
@Test @Test
void testCustomScheduler1() throws Exception { void testCustomScheduler1() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>(); var ref = new AtomicReference<Executor>();
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> { ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
ref.set(scheduler(Thread.currentThread())); Thread thread = factory.newThread(() -> {
}).join(); ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
});
thread.start();
thread.join();
assertTrue(ref.get() == scheduler1); assertTrue(ref.get() == scheduler1);
} }
@ -77,17 +84,7 @@ class CustomScheduler {
*/ */
@Test @Test
void testCustomScheduler2() throws Exception { void testCustomScheduler2() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>(); VThreadRunner.run(this::testCustomScheduler1);
Thread.ofVirtual().start(() -> {
try {
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
ref.set(scheduler(Thread.currentThread()));
}).join();
} catch (Exception e) {
e.printStackTrace();
}
}).join();
assertTrue(ref.get() == scheduler1);
} }
/** /**
@ -96,16 +93,19 @@ class CustomScheduler {
*/ */
@Test @Test
void testCustomScheduler3() throws Exception { void testCustomScheduler3() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>(); var ref = new AtomicReference<Executor>();
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> { ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
Thread thread = factory.newThread(() -> {
try { try {
Thread.ofVirtual().start(() -> { Thread.ofVirtual().start(() -> {
ref.set(scheduler(Thread.currentThread())); ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
}).join(); }).join();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
}).join(); });
thread.start();
thread.join();
assertTrue(ref.get() == scheduler1); assertTrue(ref.get() == scheduler1);
} }
@ -115,16 +115,22 @@ class CustomScheduler {
*/ */
@Test @Test
void testCustomScheduler4() throws Exception { void testCustomScheduler4() throws Exception {
AtomicReference<Executor> ref = new AtomicReference<>(); var ref = new AtomicReference<Executor>();
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> { ThreadFactory factory1 = VThreadScheduler.virtualThreadFactory(scheduler1);
ThreadFactory factory2 = VThreadScheduler.virtualThreadFactory(scheduler2);
Thread thread1 = factory1.newThread(() -> {
try { try {
ThreadBuilders.virtualThreadBuilder(scheduler2).start(() -> { Thread thread2 = factory2.newThread(() -> {
ref.set(scheduler(Thread.currentThread())); ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
}).join(); });
thread2.start();
thread2.join();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
}).join(); });
thread1.start();
thread1.join();
assertTrue(ref.get() == scheduler2); assertTrue(ref.get() == scheduler2);
} }
@ -149,8 +155,9 @@ class CustomScheduler {
} }
assertTrue(exc.get() instanceof WrongThreadException); assertTrue(exc.get() instanceof WrongThreadException);
}; };
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
ThreadBuilders.virtualThreadBuilder(scheduler).start(LockSupport::park); Thread thread = factory.newThread(LockSupport::park);
thread.start();
} }
/** /**
@ -162,11 +169,12 @@ class CustomScheduler {
Thread carrier = Thread.currentThread(); Thread carrier = Thread.currentThread();
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread"); assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
try { try {
var builder = ThreadBuilders.virtualThreadBuilder(Runnable::run); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
Thread vthread = builder.start(() -> { Thread vthread = factory.newThread(() -> {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
Thread.yield(); Thread.yield();
}); });
vthread.start();
assertTrue(vthread.isInterrupted()); assertTrue(vthread.isInterrupted());
assertFalse(carrier.isInterrupted()); assertFalse(carrier.isInterrupted());
} finally { } finally {
@ -183,10 +191,11 @@ class CustomScheduler {
Thread carrier = Thread.currentThread(); Thread carrier = Thread.currentThread();
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread"); assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
try { try {
var builder = ThreadBuilders.virtualThreadBuilder(Runnable::run); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
Thread vthread = builder.start(() -> { Thread vthread = factory.newThread(() -> {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
}); });
vthread.start();
assertTrue(vthread.isInterrupted()); assertTrue(vthread.isInterrupted());
assertFalse(carrier.isInterrupted()); assertFalse(carrier.isInterrupted());
} finally { } finally {
@ -204,11 +213,13 @@ class CustomScheduler {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
task.run(); task.run();
}; };
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
try { try {
AtomicBoolean interrupted = new AtomicBoolean(); AtomicBoolean interrupted = new AtomicBoolean();
Thread vthread = ThreadBuilders.virtualThreadBuilder(scheduler).start(() -> { Thread vthread = factory.newThread(() -> {
interrupted.set(Thread.currentThread().isInterrupted()); interrupted.set(Thread.currentThread().isInterrupted());
}); });
vthread.start();
assertFalse(vthread.isInterrupted()); assertFalse(vthread.isInterrupted());
} finally { } finally {
Thread.interrupted(); 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) { @Test
if (!thread.isVirtual()) void testThreadStartOOME() throws Exception {
throw new IllegalArgumentException("Not a virtual thread"); Executor scheduler = task -> {
try { System.err.println("OutOfMemoryError");
Field scheduler = Class.forName("java.lang.VirtualThread") throw new OutOfMemoryError();
.getDeclaredField("scheduler"); };
scheduler.setAccessible(true); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
return (Executor) scheduler.get(thread); Thread thread = factory.newThread(() -> { });
} catch (Exception e) { assertThrows(OutOfMemoryError.class, thread::start);
throw new RuntimeException(e); }
/**
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.RejectedExecutionException;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -52,15 +50,25 @@ import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile; import jdk.jfr.consumer.RecordingFile;
import jdk.test.lib.thread.VThreadPinner; 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.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest; 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 static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class JfrEvents { class JfrEvents {
@BeforeAll
static void setup() {
int minParallelism = 2;
if (Thread.currentThread().isVirtual()) {
minParallelism++;
}
VThreadRunner.ensureParallelism(minParallelism);
}
/** /**
* Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events. * Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
*/ */
@ -93,100 +101,42 @@ class JfrEvents {
} }
/** /**
* Arguments for testVirtualThreadPinned to test jdk.VirtualThreadPinned event. * Test jdk.VirtualThreadPinned event when parking while pinned.
* [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.
*/ */
@ParameterizedTest @ParameterizedTest
@MethodSource("pinnedCases") @ValueSource(booleans = { true, false })
void testVirtualThreadPinned(String label, void testParkWhenPinned(boolean timed) throws Exception {
ThrowingRunnable<Exception> parker,
Thread.State expectedState,
Consumer<Thread> unparker) throws Exception {
try (Recording recording = new Recording()) { try (Recording recording = new Recording()) {
recording.enable("jdk.VirtualThreadPinned"); recording.enable("jdk.VirtualThreadPinned");
recording.start(); recording.start();
try {
var exception = new AtomicReference<Throwable>(); var started = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var done = new AtomicBoolean();
try { var vthread = Thread.startVirtualThread(() -> {
parker.run(); VThreadPinner.runPinned(() -> {
} catch (Throwable e) { started.set(true);
exception.set(e); while (!done.get()) {
if (timed) {
LockSupport.parkNanos(Long.MAX_VALUE);
} else {
LockSupport.park();
}
} }
}); });
});
try { try {
// wait for thread to park/wait // wait for thread to start and park
Thread.State state = thread.getState(); awaitTrue(started);
while (state != expectedState) { await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
assertTrue(state != Thread.State.TERMINATED, thread.toString());
Thread.sleep(10);
state = thread.getState();
}
} finally {
unparker.accept(thread);
thread.join();
assertNull(exception.get());
}
} finally { } finally {
done.set(true);
LockSupport.unpark(vthread);
vthread.join();
recording.stop(); recording.stop();
} }
Map<String, Integer> events = sumEvents(recording); assertContainsPinnedEvent(recording, vthread);
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");
} }
} }
@ -203,16 +153,14 @@ class JfrEvents {
Executor scheduler = task -> pool.execute(task); Executor scheduler = task -> pool.execute(task);
// create virtual thread that uses custom scheduler // create virtual thread that uses custom scheduler
ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler).factory(); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
// start a thread // start a thread
Thread thread = factory.newThread(LockSupport::park); Thread thread = factory.newThread(LockSupport::park);
thread.start(); thread.start();
// wait for thread to park // wait for thread to park
while (thread.getState() != Thread.State.WAITING) { await(thread, Thread.State.WAITING);
Thread.sleep(10);
}
// shutdown scheduler // shutdown scheduler
pool.shutdown(); pool.shutdown();
@ -232,14 +180,23 @@ class JfrEvents {
recording.stop(); recording.stop();
} }
Map<String, Integer> events = sumEvents(recording); List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
System.err.println(events); System.err.println(submitFailedEvents);
assertTrue(submitFailedEvents.size() == 2, "Expected two events");
int count = events.getOrDefault("jdk.VirtualThreadSubmitFailed", 0);
assertEquals(2, count);
} }
} }
/**
* 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. * Read the events from the recording and return a map of event name to count.
*/ */
@ -264,4 +221,38 @@ class JfrEvents {
} }
return recordingFile; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -21,41 +21,78 @@
* questions. * questions.
*/ */
/** /*
* @test * @test id=default
* @summary Test virtual threads using Object.wait/notifyAll * @summary Test virtual threads using Object.wait/notifyAll
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib * @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.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test; 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.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
class MonitorWaitNotify { class MonitorWaitNotify {
@BeforeAll
static void setup() {
// need >=2 carriers for testing pinning
VThreadRunner.ensureParallelism(2);
}
/** /**
* Test virtual thread waits, notified by platform thread. * Test virtual thread waits, notified by platform thread.
*/ */
@Test @ParameterizedTest
void testWaitNotify1() throws Exception { @ValueSource(booleans = { true, false })
void testWaitNotify1(boolean pinned) throws Exception {
var lock = new Object(); var lock = new Object();
var ready = new Semaphore(0); var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
ready.release();
try { try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(); lock.wait();
});
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
}); });
// thread invokes notify awaitTrue(ready);
ready.acquire();
// notify, thread should block waiting to reenter
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
await(thread, Thread.State.BLOCKED);
} }
thread.join(); thread.join();
} }
@ -66,15 +103,13 @@ class MonitorWaitNotify {
@Test @Test
void testWaitNotify2() throws Exception { void testWaitNotify2() throws Exception {
var lock = new Object(); var lock = new Object();
var ready = new Semaphore(0); var thread = Thread.ofVirtual().unstarted(() -> {
var thread = Thread.ofVirtual().start(() -> {
ready.acquireUninterruptibly();
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
} }
}); });
synchronized (lock) { synchronized (lock) {
ready.release(); thread.start();
lock.wait(); lock.wait();
} }
thread.join(); thread.join();
@ -83,89 +118,447 @@ class MonitorWaitNotify {
/** /**
* Test virtual thread waits, notified by another virtual thread. * Test virtual thread waits, notified by another virtual thread.
*/ */
@Test @ParameterizedTest
void testWaitNotify3() throws Exception { @ValueSource(booleans = { true, false })
// need at least two carrier threads due to pinning void testWaitNotify3(boolean pinned) throws Exception {
int previousParallelism = VThreadRunner.ensureParallelism(2);
try {
var lock = new Object(); var lock = new Object();
var ready = new Semaphore(0); var ready = new AtomicBoolean();
var thread1 = Thread.ofVirtual().start(() -> { var thread1 = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
ready.release();
try { try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(); lock.wait();
} catch (InterruptedException e) { } });
} else {
ready.set(true);
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}); });
var thread2 = Thread.ofVirtual().start(() -> { var thread2 = Thread.ofVirtual().start(() -> {
ready.acquireUninterruptibly(); try {
awaitTrue(ready);
// notify, thread should block waiting to reenter
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
await(thread1, Thread.State.BLOCKED);
}
} catch (InterruptedException e) {
e.printStackTrace();
} }
}); });
thread1.join(); thread1.join();
thread2.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 { } finally {
// restore synchronized (lock) {
VThreadRunner.setParallelism(previousParallelism); 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 interrupt status set when calling Object.wait. * 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 @Test
void testWaitNotify4() throws Exception { 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(() -> { VThreadRunner.run(() -> {
Thread t = Thread.currentThread();
t.interrupt();
Object lock = new Object(); Object lock = new Object();
synchronized (lock) { synchronized (lock) {
try { Thread.currentThread().interrupt();
lock.wait(); if (timeout > 0) {
fail(); assertThrows(InterruptedException.class, () -> lock.wait(timeout));
} catch (InterruptedException e) { } else {
// interrupt status should be cleared assertThrows(InterruptedException.class, lock::wait);
assertFalse(t.isInterrupted());
} }
assertFalse(Thread.currentThread().isInterrupted());
} }
}); });
} }
/** /**
* Test interrupt when blocked in Object.wait. * 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 @Test
void testWaitNotify5() throws Exception { void testParkingPermitNotConsumed() throws Exception {
VThreadRunner.run(() -> { var lock = new Object();
Thread t = Thread.currentThread(); var started = new CountDownLatch(1);
scheduleInterrupt(t, 1000); var completed = new AtomicBoolean();
Object lock = new Object(); var vthread = Thread.ofVirtual().start(() -> {
started.countDown();
LockSupport.unpark(Thread.currentThread());
synchronized (lock) { synchronized (lock) {
try { try {
lock.wait(); lock.wait();
fail();
} catch (InterruptedException e) { } catch (InterruptedException e) {
// interrupt status should be cleared fail("wait interrupted");
assertFalse(t.isInterrupted());
} }
} }
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));
}
}); });
} }
/** /**
* Schedule a thread to be interrupted after a delay. * Test that wait throws IMSE when not owner.
*/ */
private static void scheduleInterrupt(Thread thread, long delay) { @Test
Runnable interruptTask = () -> { void testIllegalMonitorStateException() throws Exception {
try { VThreadRunner.run(() -> {
Thread.sleep(delay); Object obj = new Object();
thread.interrupt(); assertThrows(IllegalMonitorStateException.class, () -> obj.wait());
} catch (Exception e) { assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0));
e.printStackTrace(); assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000));
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE));
});
} }
};
new Thread(interruptTask).start(); /**
* Waits for the boolean value to become true.
*/
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
while (!ref.get()) {
Thread.sleep(20);
}
}
/**
* 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();
}
}
/**
* Returns the current time in milliseconds.
*/
private static long millisTime() {
long now = System.nanoTime();
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
}
/**
* 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 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 * @summary Test virtual thread park when scheduler is a fixed thread pool
* @requires vm.continuations * @requires vm.continuations
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib
* @run main ParkWithFixedThreadPool * @run main ParkWithFixedThreadPool
*/ */
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.*; import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
public class ParkWithFixedThreadPool { public class ParkWithFixedThreadPool {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
@ -58,9 +60,7 @@ public class ParkWithFixedThreadPool {
} }
}; };
ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler) ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
.name("vthread-", 0)
.factory();
for (int i = 0; i < vthreadCount; i++) { for (int i = 0; i < vthreadCount; i++) {
vthreads[i] = factory.newThread(target); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.ThreadFactory;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
import jdk.test.lib.thread.VThreadRunner; import jdk.test.lib.thread.VThreadRunner;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -142,11 +143,10 @@ class Reflection {
*/ */
@Test @Test
void testInvokeStatic6() throws Exception { 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"); Method parkMethod = Parker.class.getDeclaredMethod("park");
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
ThreadFactory factory = builder.factory();
var ready = new CountDownLatch(1); var ready = new CountDownLatch(1);
Thread vthread = factory.newThread(() -> { Thread vthread = factory.newThread(() -> {
@ -321,11 +321,10 @@ class Reflection {
*/ */
@Test @Test
void testNewInstance6() throws Exception { 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(); Constructor<?> ctor = Parker.class.getDeclaredConstructor();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
ThreadFactory factory = builder.factory();
var ready = new CountDownLatch(1); var ready = new CountDownLatch(1);
Thread vthread = factory.newThread(() -> { 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.util.stream.Stream;
import java.nio.channels.Selector; import java.nio.channels.Selector;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner; 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.Test;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; 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.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*; import static org.junit.jupiter.api.Assumptions.*;
@ -78,9 +81,12 @@ class ThreadAPI {
private static ScheduledExecutorService scheduler; private static ScheduledExecutorService scheduler;
@BeforeAll @BeforeAll
static void setup() throws Exception { static void setup() {
ThreadFactory factory = Executors.defaultThreadFactory(); ThreadFactory factory = Executors.defaultThreadFactory();
scheduler = Executors.newSingleThreadScheduledExecutor(factory); scheduler = Executors.newSingleThreadScheduledExecutor(factory);
// need >=2 carriers for testing pinning
VThreadRunner.ensureParallelism(2);
} }
@AfterAll @AfterAll
@ -719,14 +725,7 @@ class ThreadAPI {
*/ */
@Test @Test
void testJoin34() throws Exception { void testJoin34() throws Exception {
// need at least two carrier threads due to pinning
int previousParallelism = VThreadRunner.ensureParallelism(2);
try {
VThreadRunner.run(this::testJoin33); VThreadRunner.run(this::testJoin33);
} finally {
// restore
VThreadRunner.setParallelism(previousParallelism);
}
} }
/** /**
@ -1084,11 +1083,10 @@ class ThreadAPI {
*/ */
@Test @Test
void testYield1() throws Exception { 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>(); var list = new CopyOnWriteArrayList<String>();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
ThreadFactory factory = builder.factory();
var thread = factory.newThread(() -> { var thread = factory.newThread(() -> {
list.add("A"); list.add("A");
var child = factory.newThread(() -> { var child = factory.newThread(() -> {
@ -1112,11 +1110,10 @@ class ThreadAPI {
*/ */
@Test @Test
void testYield2() throws Exception { 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>(); var list = new CopyOnWriteArrayList<String>();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
ThreadFactory factory = builder.factory();
var thread = factory.newThread(() -> { var thread = factory.newThread(() -> {
list.add("A"); list.add("A");
var child = factory.newThread(() -> { var child = factory.newThread(() -> {
@ -1708,50 +1705,59 @@ class ThreadAPI {
*/ */
@Test @Test
void testGetState4() throws Exception { void testGetState4() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers"); assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
AtomicBoolean completed = new AtomicBoolean(); AtomicBoolean completed = new AtomicBoolean();
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread t1 = builder.start(() -> { Thread thread1 = factory.newThread(() -> {
Thread t2 = builder.unstarted(LockSupport::park); Thread thread2 = factory.newThread(LockSupport::park);
assertEquals(Thread.State.NEW, t2.getState()); assertEquals(Thread.State.NEW, thread2.getState());
// start t2 to make it runnable // start t2 to make it runnable
t2.start(); thread2.start();
try { try {
assertEquals(Thread.State.RUNNABLE, t2.getState()); assertEquals(Thread.State.RUNNABLE, thread2.getState());
// yield to allow t2 to run and park // yield to allow t2 to run and park
Thread.yield(); Thread.yield();
assertEquals(Thread.State.WAITING, t2.getState()); assertEquals(Thread.State.WAITING, thread2.getState());
} finally { } finally {
// unpark t2 to make it runnable again // unpark t2 to make it runnable again
LockSupport.unpark(t2); LockSupport.unpark(thread2);
} }
// t2 should be runnable (not mounted) // t2 should be runnable (not mounted)
assertEquals(Thread.State.RUNNABLE, t2.getState()); assertEquals(Thread.State.RUNNABLE, thread2.getState());
completed.set(true); completed.set(true);
}); });
t1.join(); thread1.start();
thread1.join();
} }
assertTrue(completed.get() == true); 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 @ParameterizedTest
void testGetState5() throws Exception { @ValueSource(booleans = { true, false })
var started = new CountDownLatch(1); void testGetState5(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().unstarted(() -> { var thread = Thread.ofVirtual().unstarted(() -> {
started.countDown(); if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
synchronized (lock) { } synchronized (lock) { }
}); });
} else {
ready.set(true);
synchronized (lock) { }
}
});
synchronized (lock) { synchronized (lock) {
thread.start(); thread.start();
started.await(); awaitTrue(ready);
// wait for thread to block // wait for thread to block
await(thread, Thread.State.BLOCKED); await(thread, Thread.State.BLOCKED);
@ -1762,16 +1768,35 @@ class ThreadAPI {
/** /**
* Test Thread::getState when thread is waiting in Object.wait. * Test Thread::getState when thread is waiting in Object.wait.
*/ */
@Test @ParameterizedTest
void testGetState6() throws Exception { @ValueSource(booleans = { true, false })
void testGetState6(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { 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 { try {
// wait for thread to wait // wait for thread to wait
awaitTrue(ready);
await(thread, Thread.State.WAITING); await(thread, Thread.State.WAITING);
// notify, thread should block trying to reenter
synchronized (lock) {
lock.notifyAll();
await(thread, Thread.State.BLOCKED);
}
} finally { } finally {
thread.interrupt(); thread.interrupt();
thread.join(); thread.join();
@ -1781,18 +1806,35 @@ class ThreadAPI {
/** /**
* Test Thread::getState when thread is waiting in Object.wait(millis). * Test Thread::getState when thread is waiting in Object.wait(millis).
*/ */
@Test @ParameterizedTest
void testGetState7() throws Exception { @ValueSource(booleans = { true, false })
void testGetState7(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
try { try {
if (pinned) {
VThreadPinner.runPinned(() -> {
ready.set(true);
lock.wait(Long.MAX_VALUE); lock.wait(Long.MAX_VALUE);
});
} else {
ready.set(true);
lock.wait(Long.MAX_VALUE);
}
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
}); });
try { try {
// wait for thread to wait // wait for thread to timed-wait
awaitTrue(ready);
await(thread, Thread.State.TIMED_WAITING); await(thread, Thread.State.TIMED_WAITING);
// notify, thread should block trying to reenter
synchronized (lock) {
lock.notifyAll();
await(thread, Thread.State.BLOCKED);
}
} finally { } finally {
thread.interrupt(); thread.interrupt();
thread.join(); thread.join();
@ -1933,7 +1975,7 @@ class ThreadAPI {
* Test Thread::getStackTrace on unstarted thread. * Test Thread::getStackTrace on unstarted thread.
*/ */
@Test @Test
void testGetStackTrace1() { void testGetStackTraceUnstarted() {
var thread = Thread.ofVirtual().unstarted(() -> { }); var thread = Thread.ofVirtual().unstarted(() -> { });
StackTraceElement[] stack = thread.getStackTrace(); StackTraceElement[] stack = thread.getStackTrace();
assertTrue(stack.length == 0); assertTrue(stack.length == 0);
@ -1943,136 +1985,224 @@ class ThreadAPI {
* Test Thread::getStackTrace on thread that has been started but has not run. * Test Thread::getStackTrace on thread that has been started but has not run.
*/ */
@Test @Test
void testGetStackTrace2() throws Exception { void testGetStackTraceStarted() throws Exception {
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers"); assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
Executor scheduler = task -> { }; Executor scheduler = task -> { };
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread thread = builder.start(() -> { }); Thread thread = factory.newThread(() -> { });
thread.start();
StackTraceElement[] stack = thread.getStackTrace(); StackTraceElement[] stack = thread.getStackTrace();
assertTrue(stack.length == 0); assertTrue(stack.length == 0);
} }
/** /**
* Test Thread::getStackTrace on running thread. * Test Thread::getStackTrace on thread that is runnable-mounted.
*/ */
@Test @Test
void testGetStackTrace3() throws Exception { void testGetStackTraceRunnableMounted() throws Exception {
var sel = Selector.open(); var ready = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var done = new AtomicBoolean();
try { sel.select(); } catch (Exception e) { }
}); class Foo {
try { void spinUntilDone() {
while (!contains(thread.getStackTrace(), "select")) { ready.set(true);
assertTrue(thread.isAlive()); while (!done.get()) {
Thread.sleep(20); 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 { } finally {
sel.close(); done.set(true);
thread.join(); thread.join();
} }
} }
/** /**
* Test Thread::getStackTrace on thread waiting in Object.wait. * Test Thread::getStackTrace on thread that is runnable-unmounted.
*/ */
@Test @Test
void testGetStackTrace4() throws Exception { void testGetStackTraceRunnableUnmounted() 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 -> {
pool.submit(() -> {
ref.set(Thread.currentThread());
task.run();
});
};
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); // custom scheduler with one carrier thread
Thread vthread = builder.start(() -> { try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
synchronized (lock) { ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
try {
lock.wait(); // start thread1 to park
} catch (Exception e) { } 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);
}
// 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
void testGetStackTrace5() throws Exception {
var thread = Thread.ofVirtual().start(LockSupport::park);
await(thread, Thread.State.WAITING);
try { try {
StackTraceElement[] stack = thread.getStackTrace(); // 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")); assertTrue(contains(stack, "LockSupport.park"));
} finally { } finally {
LockSupport.unpark(thread); done.set(true);
thread.join(); }
} }
} }
/** /**
* Test Thread::getStackTrace on timed-parked thread. * Test Thread::getStackTrace on thread blocked on monitor enter.
*/ */
@Test @ParameterizedTest
void testGetStackTrace6() throws Exception { @ValueSource(booleans = { true, false })
var thread = Thread.ofVirtual().start(() -> { void testGetStackTraceBlocked(boolean pinned) throws Exception {
LockSupport.parkNanos(Long.MAX_VALUE); class Foo {
}); void enter() {
await(thread, Thread.State.TIMED_WAITING); synchronized (this) { }
try {
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.parkNanos"));
} finally {
LockSupport.unpark(thread);
thread.join();
} }
} }
Foo foo = new Foo();
/** var ready = new AtomicBoolean();
* Test Thread::getStackTrace on parked thread that is pinned. var thread = Thread.ofVirtual().unstarted(() -> {
*/ if (pinned) {
@Test
void testGetStackTrace7() throws Exception {
AtomicBoolean done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> { VThreadPinner.runPinned(() -> {
ready.set(true);
foo.enter();
});
} else {
ready.set(true);
foo.enter();
}
});
synchronized (foo) {
thread.start();
awaitTrue(ready);
// wait for thread to block
await(thread, Thread.State.BLOCKED);
StackTraceElement[] stack = thread.getStackTrace();
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 {
thread.interrupt();
thread.join();
}
}
/**
* Test Thread::getStackTrace when thread is waiting in timed-Object.wait.
*/
@ParameterizedTest
@ValueSource(booleans = { true, false })
void testGetStackTraceTimedWaiting(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(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()) { while (!done.get()) {
LockSupport.park(); LockSupport.park();
} }
}); });
} else {
ready.set(true);
while (!done.get()) {
LockSupport.park();
}
}
}); });
await(thread, Thread.State.WAITING);
try { try {
// wait for thread to park
awaitTrue(ready);
await(thread, Thread.State.WAITING);
StackTraceElement[] stack = thread.getStackTrace(); StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.park")); assertTrue(contains(stack, "LockSupport.park"));
} finally { } 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 @ParameterizedTest
void testGetStackTrace8() throws Exception { @ValueSource(booleans = { true, false })
AtomicBoolean done = new AtomicBoolean(); void testGetStackTraceTimedPark(boolean pinned) throws Exception {
var ready = new AtomicBoolean();
var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
if (pinned) {
ready.set(true);
VThreadPinner.runPinned(() -> { VThreadPinner.runPinned(() -> {
while (!done.get()) { while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE); LockSupport.parkNanos(Long.MAX_VALUE);
} }
}); });
} else {
ready.set(true);
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
}
}); });
await(thread, Thread.State.TIMED_WAITING);
try { try {
// wait for thread to park
awaitTrue(ready);
await(thread, Thread.State.TIMED_WAITING);
StackTraceElement[] stack = thread.getStackTrace(); StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.parkNanos")); assertTrue(contains(stack, "LockSupport.parkNanos"));
} finally { } finally {
@ -2110,7 +2253,7 @@ class ThreadAPI {
* Test Thread::getStackTrace on terminated thread. * Test Thread::getStackTrace on terminated thread.
*/ */
@Test @Test
void testGetStackTrace9() throws Exception { void testGetStackTraceTerminated() throws Exception {
var thread = Thread.ofVirtual().start(() -> { }); var thread = Thread.ofVirtual().start(() -> { });
thread.join(); thread.join();
StackTraceElement[] stack = thread.getStackTrace(); StackTraceElement[] stack = thread.getStackTrace();
@ -2133,7 +2276,7 @@ class ThreadAPI {
*/ */
@Test @Test
void testGetAllStackTraces2() throws Exception { 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)) { try (ForkJoinPool pool = new ForkJoinPool(1)) {
AtomicReference<Thread> ref = new AtomicReference<>(); AtomicReference<Thread> ref = new AtomicReference<>();
Executor scheduler = task -> { Executor scheduler = task -> {
@ -2143,14 +2286,15 @@ class ThreadAPI {
}); });
}; };
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
Thread vthread = builder.start(() -> { Thread vthread = factory.newThread(() -> {
synchronized (lock) { synchronized (lock) {
try { try {
lock.wait(); lock.wait();
} catch (Exception e) { } } catch (Exception e) { }
} }
}); });
vthread.start();
// get carrier Thread // get carrier Thread
Thread carrier; Thread carrier;
@ -2168,8 +2312,9 @@ class ThreadAPI {
synchronized (lock) { synchronized (lock) {
lock.notifyAll(); lock.notifyAll();
} }
vthread.join();
// get stack trace for the carrier thread // stack trace for the carrier thread
StackTraceElement[] stackTrace = map.get(carrier); StackTraceElement[] stackTrace = map.get(carrier);
assertNotNull(stackTrace); assertNotNull(stackTrace);
assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool")); 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. * 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,7 +24,7 @@
/** /**
* @test * @test
* @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws * @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 * @library /test/lib
* @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java * @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java
* @run junit/othervm --enable-native-access=ALL-UNNAMED VirtualThreadPinnedEventThrows * @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 java.util.concurrent.locks.LockSupport;
import jdk.internal.event.VirtualThreadPinnedEvent; import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner; import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class VirtualThreadPinnedEventThrows { 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. * 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,17 +25,18 @@
* @test * @test
* @summary Stress parking with CompletableFuture timed get * @summary Stress parking with CompletableFuture timed get
* @requires vm.debug != true & vm.continuations * @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.Duration;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class TimedGet { public class CompletableFutureTimedGet {
static final String RESULT = "foo"; static final String RESULT = "foo";
@ -77,30 +78,27 @@ public class TimedGet {
// wait for all threads to terminate // wait for all threads to terminate
long lastTimestamp = System.currentTimeMillis(); long lastTimestamp = System.currentTimeMillis();
int i = 0; boolean done;
while (i < threadCount) { do {
Thread t = threads.get(i); done = true;
boolean terminated; for (Thread t : threads) {
if (t.isAlive()) { if (!t.join(Duration.ofSeconds(1))) {
terminated = t.join(Duration.ofMillis(500)); done = false;
}
}
// print trace message so the output tracks progress // print trace message so the output tracks progress
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
if ((currentTime - lastTimestamp) > 500) { if (done || ((currentTime - lastTimestamp) > 500)) {
System.out.println(completed.get()); System.out.format("%s => completed %d of %d%n",
Instant.now(), completed.get(), threadCount);
lastTimestamp = currentTime; lastTimestamp = currentTime;
} }
} else {
terminated = true; } while (!done);
}
if (terminated) {
i++;
}
}
// all tasks should have completed successfully // all tasks should have completed successfully
int completedCount = completed.get(); int completedCount = completed.get();
System.out.println(completedCount);
if (completedCount != threadCount) { if (completedCount != threadCount) {
throw new RuntimeException("completed = " + completedCount); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,30 +23,32 @@
/** /**
* @test * @test
* @summary Stress test asynchronous Thread.getStackTrace * @summary Stress test asynchronous Thread.getStackTrace when parking
* @requires vm.debug != true & vm.continuations * @requires vm.debug != true & vm.continuations
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @compile GetStackTraceALot.java ../ThreadBuilders.java * @library /test/lib
* @run main GetStackTraceALot * @run main GetStackTraceALotWhenParking
*/ */
/** /**
* @test * @test
* @requires vm.debug == true & vm.continuations * @requires vm.debug == true & vm.continuations
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @compile GetStackTraceALot.java ../ThreadBuilders.java * @library /test/lib
* @run main/timeout=300 GetStackTraceALot 1000 * @run main/timeout=300 GetStackTraceALotWhenParking 1000
*/ */
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadScheduler;
public class GetStackTraceALot { public class GetStackTraceALotWhenParking {
static class RoundRobinExecutor implements Executor, AutoCloseable { static class RoundRobinExecutor implements Executor, AutoCloseable {
private final ExecutorService[] executors; private final ExecutorService[] executors;
private int next; private int next;
@ -83,7 +85,9 @@ public class GetStackTraceALot {
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
try (RoundRobinExecutor executor = new RoundRobinExecutor()) { try (RoundRobinExecutor executor = new RoundRobinExecutor()) {
Thread thread = ThreadBuilders.virtualThreadBuilder(executor).start(() -> { ThreadFactory factory = VThreadScheduler.virtualThreadFactory(executor);
Thread thread = factory.newThread(() -> {
while (count.incrementAndGet() < ITERATIONS) { while (count.incrementAndGet() < ITERATIONS) {
long start = System.nanoTime(); long start = System.nanoTime();
while ((System.nanoTime() - start) < SPIN_NANOS) { while ((System.nanoTime() - start) < SPIN_NANOS) {
@ -92,6 +96,7 @@ public class GetStackTraceALot {
LockSupport.parkNanos(500_000); LockSupport.parkNanos(500_000);
} }
}); });
thread.start();
long start = System.nanoTime(); long start = System.nanoTime();
while (thread.isAlive()) { while (thread.isAlive()) {
@ -99,7 +104,7 @@ public class GetStackTraceALot {
// printStackTrace(stackTrace); // printStackTrace(stackTrace);
Thread.sleep(5); Thread.sleep(5);
if ((System.nanoTime() - start) > 500_000_000) { 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(); start = System.nanoTime();
} }
} }

View File

@ -28,7 +28,7 @@
* @requires vm.debug != true * @requires vm.debug != true
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib * @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 * @requires vm.debug == true
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib * @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; import java.time.Instant;
@ -79,7 +79,7 @@ public class GetStackTraceALotWhenPinned {
}); });
long lastTimestamp = System.currentTimeMillis(); long lastTimestamp = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) { for (int i = 1; i <= iterations; i++) {
// wait for virtual thread to arrive // wait for virtual thread to arrive
barrier.await(); barrier.await();
@ -87,8 +87,8 @@ public class GetStackTraceALotWhenPinned {
LockSupport.unpark(thread); LockSupport.unpark(thread);
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
if ((currentTime - lastTimestamp) > 500) { if (i == iterations || ((currentTime - lastTimestamp) > 500)) {
System.out.format("%s %d remaining ...%n", Instant.now(), (iterations - i)); System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
lastTimestamp = currentTime; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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 class PinALot {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
int iterations = 1_000_000; int iterations;
if (args.length > 0) { if (args.length > 0) {
iterations = Integer.parseInt(args[0]); iterations = Integer.parseInt(args[0]);
} else {
iterations = 1_000_000;
} }
final int ITERATIONS = iterations;
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> { Thread thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> { VThreadPinner.runPinned(() -> {
while (count.incrementAndGet() < ITERATIONS) { while (count.incrementAndGet() < iterations) {
LockSupport.parkNanos(1); LockSupport.parkNanos(1);
} }
}); });
@ -65,12 +65,12 @@ public class PinALot {
boolean terminated; boolean terminated;
do { do {
terminated = thread.join(Duration.ofSeconds(1)); terminated = thread.join(Duration.ofSeconds(1));
System.out.println(Instant.now() + " => " + count.get()); System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
} while (!terminated); } while (!terminated);
int countValue = count.get(); int countValue = count.get();
if (countValue != ITERATIONS) { if (countValue != iterations) {
throw new RuntimeException("count = " + countValue); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -36,6 +36,7 @@
*/ */
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.SynchronousQueue; import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -91,11 +92,12 @@ public class PingPong {
boolean terminated; boolean terminated;
do { do {
terminated = t1.join(Duration.ofMillis(500)); terminated = t1.join(Duration.ofMillis(500));
if (terminated) if (terminated) {
terminated = t2.join(Duration.ofMillis(500)); 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); } while (!terminated);
} }
interface Exchanger<E> { 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -69,7 +69,7 @@ public class Skynet {
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
System.out.format("Result: %d in %s ms%n", sum, (end-start)); System.out.format("Result: %d in %s ms%n", sum, (end-start));
if (sum != expected) 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) { 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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 class SleepALot {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
int iterations = 1_000_000; int iterations;
if (args.length > 0) { if (args.length > 0) {
iterations = Integer.parseInt(args[0]); iterations = Integer.parseInt(args[0]);
} else {
iterations = 1_000_000;
} }
final int ITERATIONS = iterations;
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> { Thread thread = Thread.ofVirtual().start(() -> {
while (count.incrementAndGet() < ITERATIONS) { while (count.incrementAndGet() < iterations) {
try { try {
Thread.sleep(Duration.ofNanos(100)); Thread.sleep(Duration.ofNanos(100));
} catch (InterruptedException ignore) { } } catch (InterruptedException ignore) { }
@ -60,12 +60,12 @@ public class SleepALot {
boolean terminated; boolean terminated;
do { do {
terminated = thread.join(Duration.ofSeconds(1)); terminated = thread.join(Duration.ofSeconds(1));
System.out.println(Instant.now() + " => " + count.get()); System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
} while (!terminated); } while (!terminated);
int countValue = count.get(); int countValue = count.get();
if (countValue != ITERATIONS) { if (countValue != iterations) {
throw new RuntimeException("count = " + countValue); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,7 @@
* @test * @test
* @summary Stress test Thread.yield * @summary Stress test Thread.yield
* @requires vm.debug != true * @requires vm.debug != true
* @run main YieldALot 350000 * @run main YieldALot 500000
*/ */
/* /*
@ -35,34 +35,35 @@
*/ */
import java.time.Duration; import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
public class YieldALot { public class YieldALot {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
int iterations = 1_000_000; int iterations;
if (args.length > 0) { if (args.length > 0) {
iterations = Integer.parseInt(args[0]); iterations = Integer.parseInt(args[0]);
} else {
iterations = 1_000_000;
} }
final int ITERATIONS = iterations;
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> { Thread thread = Thread.ofVirtual().start(() -> {
while (count.incrementAndGet() < ITERATIONS) { while (count.incrementAndGet() < iterations) {
Thread.yield(); Thread.yield();
} }
}); });
boolean terminated; boolean terminated;
do { do {
terminated = thread.join(Duration.ofMillis(500)); terminated = thread.join(Duration.ofSeconds(1));
System.out.println(count.get()); System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
} while (!terminated); } while (!terminated);
int countValue = count.get(); int countValue = count.get();
if (countValue != ITERATIONS) { if (countValue != iterations) {
throw new RuntimeException("count = " + countValue); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.ManagementFactory;
import java.lang.management.ThreadInfo; import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean; import java.lang.management.ThreadMXBean;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.channels.Selector; import java.nio.channels.Selector;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jdk.test.lib.thread.VThreadPinner;
import jdk.test.lib.thread.VThreadRunner; import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadScheduler;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
@ -195,7 +196,7 @@ public class VirtualThreads {
*/ */
@Test @Test
void testGetThreadInfoCarrierThread() throws Exception { 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)) { try (ExecutorService pool = Executors.newFixedThreadPool(1)) {
var carrierRef = new AtomicReference<Thread>(); var carrierRef = new AtomicReference<Thread>();
Executor scheduler = (task) -> { Executor scheduler = (task) -> {
@ -204,19 +205,30 @@ public class VirtualThreads {
task.run(); task.run();
}); });
}; };
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
// start virtual thread so carrier Thread can be captured // 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(); Thread carrier = carrierRef.get();
assertTrue(carrier != null && !carrier.isVirtual()); assertTrue(carrier != null && !carrier.isVirtual());
try (Selector sel = Selector.open()) { try (Selector sel = Selector.open()) {
// start virtual thread that blocks in a native method String selClassName = sel.getClass().getName();
virtualThreadBuilder(scheduler).start(() -> {
// start virtual thread that blocks while pinned
Thread vthread = factory.newThread(() -> {
try { try {
sel.select(); VThreadPinner.runPinned(sel::select);
} catch (Exception e) { } } 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 // invoke getThreadInfo get and check the stack trace
long tid = carrier.getId(); long tid = carrier.getId();
@ -225,7 +237,7 @@ public class VirtualThreads {
// should only see carrier frames // should only see carrier frames
StackTraceElement[] stack = info.getStackTrace(); StackTraceElement[] stack = info.getStackTrace();
assertTrue(contains(stack, "java.util.concurrent.ThreadPoolExecutor")); 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 // carrier should not be holding any monitors
assertEquals(0, info.getLockedMonitors().length); assertEquals(0, info.getLockedMonitors().length);
@ -351,40 +363,4 @@ public class VirtualThreads {
.map(StackTraceElement::getClassName) .map(StackTraceElement::getClassName)
.anyMatch(className::equals); .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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -426,7 +426,7 @@ class ThreadFlockTest {
long startMillis = millisTime(); long startMillis = millisTime();
boolean done = flock.awaitAll(Duration.ofSeconds(30)); boolean done = flock.awaitAll(Duration.ofSeconds(30));
assertTrue(done); assertTrue(done);
checkDuration(startMillis, 1900, 4000); checkDuration(startMillis, 1900, 20_000);
} }
} }
@ -453,7 +453,7 @@ class ThreadFlockTest {
flock.awaitAll(Duration.ofSeconds(2)); flock.awaitAll(Duration.ofSeconds(2));
fail("awaitAll did not throw"); fail("awaitAll did not throw");
} catch (TimeoutException e) { } catch (TimeoutException e) {
checkDuration(startMillis, 1900, 4000); checkDuration(startMillis, 1900, 20_000);
} }
} finally { } finally {
latch.countDown(); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -21,28 +21,50 @@
* questions. * questions.
*/ */
package jdk.test.lib.thread;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; 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. * Tests using this class need to open java.base/java.lang.
*/ */
class ThreadBuilders { public class VThreadScheduler {
private ThreadBuilders() { } 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 { try {
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder"); Field scheduler = Class.forName("java.lang.VirtualThread")
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class); .getDeclaredField("scheduler");
ctor.setAccessible(true); scheduler.setAccessible(true);
VTBUILDER_CTOR = ctor; return (Executor) scheduler.get(thread);
} catch (Exception e) { } 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. * Returns a builder to create virtual threads that use the given scheduler.
* @throws UnsupportedOperationException if custom schedulers are not supported * @throws UnsupportedOperationException if custom schedulers are not supported
*/ */
static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) { public static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
try { 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) { } catch (InvocationTargetException e) {
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause instanceof RuntimeException re) { 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() { public static ThreadFactory virtualThreadFactory(Executor scheduler) {
try (var pool = Executors.newCachedThreadPool()) { return virtualThreadBuilder(scheduler).factory();
try {
virtualThreadBuilder(pool);
return true;
} catch (UnsupportedOperationException e) {
return false;
}
}
} }
} }