8336254: Virtual thread implementation + test updates
Reviewed-by: sspitsyn, kevinw
This commit is contained in:
parent
d3e51daf73
commit
6e228ce382
@ -871,6 +871,7 @@ ifeq ($(call isTargetOs, windows), true)
|
||||
BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT
|
||||
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c libTestJNI.c libCompleteExit.c libMonitorWithDeadObjectTest.c libTestPsig.c exeGetCreatedJavaVMs.c
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libnativeStack := java.base:libjvm
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
|
||||
else
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libsystemclssearch_agent += -lpthread
|
||||
@ -1509,6 +1510,7 @@ else
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libCompleteExit += -lpthread
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libMonitorWithDeadObjectTest += -lpthread
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libnativeStack += -lpthread
|
||||
BUILD_HOTSPOT_JTREG_LIBRARIES_JDK_LIBS_libVThreadEventTest := java.base:libjvm
|
||||
BUILD_HOTSPOT_JTREG_EXECUTABLES_LIBS_exeGetCreatedJavaVMs := -lpthread
|
||||
BUILD_HOTSPOT_JTREG_EXECUTABLES_JDK_LIBS_exeGetCreatedJavaVMs := java.base:libjvm
|
||||
|
||||
|
@ -65,7 +65,6 @@ import java.util.PropertyPermission;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
@ -2264,6 +2263,7 @@ public final class System {
|
||||
super(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
boolean attempted = Blocker.begin();
|
||||
try {
|
||||
@ -2677,14 +2677,6 @@ public final class System {
|
||||
return Thread.currentCarrierThread();
|
||||
}
|
||||
|
||||
public <V> V executeOnCarrierThread(Callable<V> task) throws Exception {
|
||||
if (Thread.currentThread() instanceof VirtualThread vthread) {
|
||||
return vthread.executeOnCarrierThread(task);
|
||||
} else {
|
||||
return task.call();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T getCarrierThreadLocal(CarrierThreadLocal<T> local) {
|
||||
return ((ThreadLocal<T>)local).getCarrierThreadLocal();
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import jdk.internal.event.VirtualThreadEndEvent;
|
||||
import jdk.internal.event.VirtualThreadPinnedEvent;
|
||||
import jdk.internal.event.VirtualThreadStartEvent;
|
||||
@ -62,14 +63,13 @@ import sun.security.action.GetPropertyAction;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
|
||||
/**
|
||||
* A thread that is scheduled by the Java virtual machine rather than the operating
|
||||
* system.
|
||||
* A thread that is scheduled by the Java virtual machine rather than the operating system.
|
||||
*/
|
||||
final class VirtualThread extends BaseVirtualThread {
|
||||
private static final Unsafe U = Unsafe.getUnsafe();
|
||||
private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
|
||||
private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
|
||||
private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler();
|
||||
private static final ScheduledExecutorService[] DELAYED_TASK_SCHEDULERS = createDelayedTaskSchedulers();
|
||||
private static final int TRACE_PINNING_MODE = tracePinningMode();
|
||||
|
||||
private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
|
||||
@ -217,7 +217,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
* on the current thread before the task runs or continues. It unmounts when the
|
||||
* task completes or yields.
|
||||
*/
|
||||
@ChangesCurrentThread
|
||||
@ChangesCurrentThread // allow mount/unmount to be inlined
|
||||
private void runContinuation() {
|
||||
// the carrier must be a platform thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
@ -257,42 +257,109 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
* Submits the runContinuation task to the scheduler. For the default scheduler,
|
||||
* and calling it on a worker thread, the task will be pushed to the local queue,
|
||||
* otherwise it will be pushed to an external submission queue.
|
||||
* @param scheduler the scheduler
|
||||
* @param retryOnOOME true to retry indefinitely if OutOfMemoryError is thrown
|
||||
* @throws RejectedExecutionException
|
||||
*/
|
||||
private void submitRunContinuation() {
|
||||
@ChangesCurrentThread
|
||||
private void submitRunContinuation(Executor scheduler, boolean retryOnOOME) {
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
try {
|
||||
// The scheduler's execute method is invoked in the context of the
|
||||
// carrier thread. For the default scheduler this ensures that the
|
||||
// current thread is a ForkJoinWorkerThread so the task will be pushed
|
||||
// to the local queue. For other schedulers, it avoids deadlock that
|
||||
// would arise due to platform and virtual threads contending for a
|
||||
// lock on the scheduler's submission queue.
|
||||
if (currentThread() instanceof VirtualThread vthread) {
|
||||
vthread.switchToCarrierThread();
|
||||
try {
|
||||
scheduler.execute(runContinuation);
|
||||
} finally {
|
||||
switchToVirtualThread(vthread);
|
||||
}
|
||||
} else {
|
||||
scheduler.execute(runContinuation);
|
||||
}
|
||||
done = true;
|
||||
} catch (RejectedExecutionException ree) {
|
||||
submitFailed(ree);
|
||||
throw ree;
|
||||
} catch (OutOfMemoryError e) {
|
||||
if (retryOnOOME) {
|
||||
U.park(false, 100_000_000); // 100ms
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the runContinuation task to given scheduler with a lazy submit.
|
||||
* If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
|
||||
* @throws RejectedExecutionException
|
||||
* @see ForkJoinPool#lazySubmit(ForkJoinTask)
|
||||
*/
|
||||
private void lazySubmitRunContinuation(ForkJoinPool pool) {
|
||||
assert Thread.currentThread() instanceof CarrierThread;
|
||||
try {
|
||||
pool.lazySubmit(ForkJoinTask.adapt(runContinuation));
|
||||
} catch (RejectedExecutionException ree) {
|
||||
submitFailed(ree);
|
||||
throw ree;
|
||||
} catch (OutOfMemoryError e) {
|
||||
submitRunContinuation(pool, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the runContinuation task to the given scheduler as an external submit.
|
||||
* If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
|
||||
* @throws RejectedExecutionException
|
||||
* @see ForkJoinPool#externalSubmit(ForkJoinTask)
|
||||
*/
|
||||
private void externalSubmitRunContinuation(ForkJoinPool pool) {
|
||||
assert Thread.currentThread() instanceof CarrierThread;
|
||||
try {
|
||||
pool.externalSubmit(ForkJoinTask.adapt(runContinuation));
|
||||
} catch (RejectedExecutionException ree) {
|
||||
submitFailed(ree);
|
||||
throw ree;
|
||||
} catch (OutOfMemoryError e) {
|
||||
submitRunContinuation(pool, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the runContinuation task to the scheduler. For the default scheduler,
|
||||
* and calling it on a worker thread, the task will be pushed to the local queue,
|
||||
* otherwise it will be pushed to an external submission queue.
|
||||
* If OutOfMemoryError is thrown then the submit will be retried until it succeeds.
|
||||
* @throws RejectedExecutionException
|
||||
*/
|
||||
private void submitRunContinuation() {
|
||||
submitRunContinuation(scheduler, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the runContinuation task to the scheduler. For the default scheduler, and
|
||||
* calling it a virtual thread that uses the default scheduler, the task will be
|
||||
* pushed to an external submission queue. This method may throw OutOfMemoryError.
|
||||
* @throws RejectedExecutionException
|
||||
* @throws OutOfMemoryError
|
||||
*/
|
||||
private void externalSubmitRunContinuationOrThrow() {
|
||||
if (scheduler == DEFAULT_SCHEDULER && currentCarrierThread() instanceof CarrierThread ct) {
|
||||
try {
|
||||
ct.getPool().externalSubmit(ForkJoinTask.adapt(runContinuation));
|
||||
} catch (RejectedExecutionException ree) {
|
||||
submitFailed(ree);
|
||||
throw ree;
|
||||
}
|
||||
} else {
|
||||
submitRunContinuation(scheduler, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,6 +452,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
@ChangesCurrentThread
|
||||
@ReservedStackAccess
|
||||
private void unmount() {
|
||||
assert !Thread.holdsLock(interruptLock);
|
||||
|
||||
// set Thread.currentThread() to return the platform thread
|
||||
Thread carrier = this.carrierThread;
|
||||
carrier.setCurrentThread(carrier);
|
||||
@ -417,7 +486,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
*/
|
||||
@ChangesCurrentThread
|
||||
@JvmtiMountTransition
|
||||
private void switchToVirtualThread(VirtualThread vthread) {
|
||||
private static void switchToVirtualThread(VirtualThread vthread) {
|
||||
Thread carrier = vthread.carrierThread;
|
||||
assert carrier == Thread.currentCarrierThread();
|
||||
carrier.setCurrentThread(vthread);
|
||||
@ -474,13 +543,12 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
|
||||
// may have been unparked while parking
|
||||
if (parkPermit && compareAndSetState(newState, UNPARKED)) {
|
||||
// lazy submit to continue on the current thread as carrier if possible
|
||||
if (currentThread() instanceof CarrierThread ct) {
|
||||
// lazy submit to continue on the current carrier if possible
|
||||
if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
|
||||
lazySubmitRunContinuation(ct.getPool());
|
||||
} else {
|
||||
submitRunContinuation();
|
||||
}
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -561,8 +629,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
// scoped values may be inherited
|
||||
inheritScopedValueBindings(container);
|
||||
|
||||
// submit task to run thread
|
||||
submitRunContinuation();
|
||||
// submit task to run thread, using externalSubmit if possible
|
||||
externalSubmitRunContinuationOrThrow();
|
||||
started = true;
|
||||
} finally {
|
||||
if (!started) {
|
||||
@ -707,7 +775,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
// need to switch to current carrier thread to avoid nested parking
|
||||
switchToCarrierThread();
|
||||
try {
|
||||
return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS);
|
||||
return schedule(this::unpark, nanos, NANOSECONDS);
|
||||
} finally {
|
||||
switchToVirtualThread(this);
|
||||
}
|
||||
@ -718,6 +786,7 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
*/
|
||||
@ChangesCurrentThread
|
||||
private void cancel(Future<?> future) {
|
||||
assert Thread.currentThread() == this;
|
||||
if (!future.isDone()) {
|
||||
// need to switch to current carrier thread to avoid nested parking
|
||||
switchToCarrierThread();
|
||||
@ -730,33 +799,26 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-enables this virtual thread for scheduling. If the virtual thread was
|
||||
* {@link #park() parked} then it will be unblocked, otherwise its next call
|
||||
* to {@code park} or {@linkplain #parkNanos(long) parkNanos} is guaranteed
|
||||
* not to block.
|
||||
* Re-enables this virtual thread for scheduling. If this virtual thread is parked
|
||||
* then its task is scheduled to continue, otherwise its next call to {@code park} or
|
||||
* {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block.
|
||||
* @throws RejectedExecutionException if the scheduler cannot accept a task
|
||||
*/
|
||||
@Override
|
||||
@ChangesCurrentThread
|
||||
void unpark() {
|
||||
Thread currentThread = Thread.currentThread();
|
||||
if (!getAndSetParkPermit(true) && currentThread != this) {
|
||||
if (!getAndSetParkPermit(true) && currentThread() != this) {
|
||||
int s = state();
|
||||
boolean parked = (s == PARKED) || (s == TIMED_PARKED);
|
||||
if (parked && compareAndSetState(s, UNPARKED)) {
|
||||
if (currentThread instanceof VirtualThread vthread) {
|
||||
vthread.switchToCarrierThread();
|
||||
try {
|
||||
|
||||
// unparked while parked
|
||||
if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) {
|
||||
submitRunContinuation();
|
||||
} finally {
|
||||
switchToVirtualThread(vthread);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
submitRunContinuation();
|
||||
}
|
||||
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
|
||||
|
||||
// unparked while parked when pinned
|
||||
if (s == PINNED || s == TIMED_PINNED) {
|
||||
// unpark carrier thread when pinned
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
disableSuspendAndPreempt();
|
||||
try {
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
Thread carrier = carrierThread;
|
||||
@ -765,8 +827,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
enableSuspendAndPreempt();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -859,11 +922,11 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
|
||||
@Override
|
||||
void blockedOn(Interruptible b) {
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
disableSuspendAndPreempt();
|
||||
try {
|
||||
super.blockedOn(b);
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
enableSuspendAndPreempt();
|
||||
}
|
||||
}
|
||||
|
||||
@ -874,9 +937,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
checkAccess();
|
||||
|
||||
// if current thread is a virtual thread then prevent it from being
|
||||
// suspended when entering or holding interruptLock
|
||||
// suspended or unmounted when entering or holding interruptLock
|
||||
Interruptible blocker;
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
disableSuspendAndPreempt();
|
||||
try {
|
||||
synchronized (interruptLock) {
|
||||
interrupted = true;
|
||||
@ -890,18 +953,22 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
if (carrier != null) carrier.setInterrupt();
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
enableSuspendAndPreempt();
|
||||
}
|
||||
|
||||
// notify blocker after releasing interruptLock
|
||||
if (blocker != null) {
|
||||
blocker.postInterrupt();
|
||||
}
|
||||
|
||||
// make available parking permit, unpark thread if parked
|
||||
unpark();
|
||||
|
||||
} else {
|
||||
interrupted = true;
|
||||
carrierThread.setInterrupt();
|
||||
setParkPermit(true);
|
||||
}
|
||||
unpark();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -914,14 +981,14 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
assert Thread.currentThread() == this;
|
||||
boolean oldValue = interrupted;
|
||||
if (oldValue) {
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
disableSuspendAndPreempt();
|
||||
try {
|
||||
synchronized (interruptLock) {
|
||||
interrupted = false;
|
||||
carrierThread.clearInterrupt();
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
enableSuspendAndPreempt();
|
||||
}
|
||||
}
|
||||
return oldValue;
|
||||
@ -946,7 +1013,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
return Thread.State.RUNNABLE;
|
||||
case RUNNING:
|
||||
// if mounted then return state of carrier thread
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
if (Thread.currentThread() != this) {
|
||||
disableSuspendAndPreempt();
|
||||
try {
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
Thread carrierThread = this.carrierThread;
|
||||
@ -955,7 +1023,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
enableSuspendAndPreempt();
|
||||
}
|
||||
}
|
||||
// runnable, mounted
|
||||
return Thread.State.RUNNABLE;
|
||||
@ -1068,31 +1137,48 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
sb.append(name);
|
||||
}
|
||||
sb.append("]/");
|
||||
Thread carrier = carrierThread;
|
||||
if (carrier != null) {
|
||||
// include the carrier thread state and name when mounted
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
|
||||
// add the carrier state and thread name when mounted
|
||||
boolean mounted;
|
||||
if (Thread.currentThread() == this) {
|
||||
mounted = appendCarrierInfo(sb);
|
||||
} else {
|
||||
disableSuspendAndPreempt();
|
||||
try {
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
carrier = carrierThread;
|
||||
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) {
|
||||
String stateAsString = carrier.threadState().toString();
|
||||
sb.append(stateAsString.toLowerCase(Locale.ROOT));
|
||||
sb.append('@');
|
||||
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
|
||||
public int hashCode() {
|
||||
@ -1127,6 +1213,22 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
return interruptLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disallow the current thread be suspended or preempted.
|
||||
*/
|
||||
private void disableSuspendAndPreempt() {
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
Continuation.pin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the current thread be suspended or preempted.
|
||||
*/
|
||||
private void enableSuspendAndPreempt() {
|
||||
Continuation.unpin();
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
}
|
||||
|
||||
// -- wrappers for get/set of state, parking permit, and carrier thread --
|
||||
|
||||
private int state() {
|
||||
@ -1188,10 +1290,16 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
private static native void registerNatives();
|
||||
static {
|
||||
registerNatives();
|
||||
|
||||
// ensure VTHREAD_GROUP is created, may be accessed by JVMTI
|
||||
var group = Thread.virtualThreadGroup();
|
||||
|
||||
// ensure VirtualThreadPinnedEvent is loaded/initialized
|
||||
U.ensureClassInitialized(VirtualThreadPinnedEvent.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the default scheduler.
|
||||
* Creates the default ForkJoinPool scheduler.
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private static ForkJoinPool createDefaultScheduler() {
|
||||
@ -1229,22 +1337,42 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the ScheduledThreadPoolExecutor used for timed unpark.
|
||||
* Schedule a runnable task to run after a delay.
|
||||
*/
|
||||
private static ScheduledExecutorService createDelayedTaskScheduler() {
|
||||
String propValue = GetPropertyAction.privilegedGetProperty("jdk.unparker.maxPoolSize");
|
||||
int poolSize;
|
||||
if (propValue != null) {
|
||||
poolSize = Integer.parseInt(propValue);
|
||||
} else {
|
||||
poolSize = 1;
|
||||
private static Future<?> schedule(Runnable command, long delay, TimeUnit unit) {
|
||||
long tid = Thread.currentThread().threadId();
|
||||
int index = (int) tid & (DELAYED_TASK_SCHEDULERS.length - 1);
|
||||
return DELAYED_TASK_SCHEDULERS[index].schedule(command, delay, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the ScheduledThreadPoolExecutors used to execute delayed tasks.
|
||||
*/
|
||||
private static ScheduledExecutorService[] createDelayedTaskSchedulers() {
|
||||
String propName = "jdk.virtualThreadScheduler.timerQueues";
|
||||
String propValue = GetPropertyAction.privilegedGetProperty(propName);
|
||||
int queueCount;
|
||||
if (propValue != null) {
|
||||
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)
|
||||
Executors.newScheduledThreadPool(poolSize, task -> {
|
||||
return InnocuousThread.newThread("VirtualThread-unparker", task);
|
||||
Executors.newScheduledThreadPool(1, task -> {
|
||||
Thread t = InnocuousThread.newThread("VirtualThread-unparker", task);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
stpe.setRemoveOnCancelPolicy(true);
|
||||
return stpe;
|
||||
schedulers[i] = stpe;
|
||||
}
|
||||
return schedulers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,9 @@
|
||||
|
||||
package java.util.concurrent.locks;
|
||||
|
||||
import jdk.internal.misc.VirtualThreads;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
|
||||
/**
|
||||
@ -176,7 +178,7 @@ public class LockSupport {
|
||||
public static void unpark(Thread thread) {
|
||||
if (thread != null) {
|
||||
if (thread.isVirtual()) {
|
||||
VirtualThreads.unpark(thread);
|
||||
JLA.unparkVirtualThread(thread);
|
||||
} else {
|
||||
U.unpark(thread);
|
||||
}
|
||||
@ -216,7 +218,7 @@ public class LockSupport {
|
||||
setBlocker(t, blocker);
|
||||
try {
|
||||
if (t.isVirtual()) {
|
||||
VirtualThreads.park();
|
||||
JLA.parkVirtualThread();
|
||||
} else {
|
||||
U.park(false, 0L);
|
||||
}
|
||||
@ -264,7 +266,7 @@ public class LockSupport {
|
||||
setBlocker(t, blocker);
|
||||
try {
|
||||
if (t.isVirtual()) {
|
||||
VirtualThreads.park(nanos);
|
||||
JLA.parkVirtualThread(nanos);
|
||||
} else {
|
||||
U.park(false, nanos);
|
||||
}
|
||||
@ -311,11 +313,7 @@ public class LockSupport {
|
||||
Thread t = Thread.currentThread();
|
||||
setBlocker(t, blocker);
|
||||
try {
|
||||
if (t.isVirtual()) {
|
||||
VirtualThreads.parkUntil(deadline);
|
||||
} else {
|
||||
U.park(true, deadline);
|
||||
}
|
||||
parkUntil(deadline);
|
||||
} finally {
|
||||
setBlocker(t, null);
|
||||
}
|
||||
@ -366,7 +364,7 @@ public class LockSupport {
|
||||
*/
|
||||
public static void park() {
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VirtualThreads.park();
|
||||
JLA.parkVirtualThread();
|
||||
} else {
|
||||
U.park(false, 0L);
|
||||
}
|
||||
@ -405,7 +403,7 @@ public class LockSupport {
|
||||
public static void parkNanos(long nanos) {
|
||||
if (nanos > 0) {
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VirtualThreads.park(nanos);
|
||||
JLA.parkVirtualThread(nanos);
|
||||
} else {
|
||||
U.park(false, nanos);
|
||||
}
|
||||
@ -444,7 +442,8 @@ public class LockSupport {
|
||||
*/
|
||||
public static void parkUntil(long deadline) {
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VirtualThreads.parkUntil(deadline);
|
||||
long millis = deadline - System.currentTimeMillis();
|
||||
JLA.parkVirtualThread(TimeUnit.MILLISECONDS.toNanos(millis));
|
||||
} else {
|
||||
U.park(true, deadline);
|
||||
}
|
||||
@ -462,4 +461,5 @@ public class LockSupport {
|
||||
private static final long PARKBLOCKER
|
||||
= U.objectFieldOffset(Thread.class, "parkBlocker");
|
||||
|
||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ import java.security.ProtectionDomain;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.stream.Stream;
|
||||
@ -504,11 +503,6 @@ public interface JavaLangAccess {
|
||||
*/
|
||||
Thread currentCarrierThread();
|
||||
|
||||
/**
|
||||
* Executes the given value returning task on the current carrier thread.
|
||||
*/
|
||||
<V> V executeOnCarrierThread(Callable<V> task) throws Exception;
|
||||
|
||||
/**
|
||||
* Returns the value of the current carrier thread's copy of a thread-local.
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -79,7 +79,7 @@ public class framecnt01 {
|
||||
}
|
||||
|
||||
// this is too fragile, implementation can change at any time.
|
||||
checkFrames(vThread1, false, 14);
|
||||
checkFrames(vThread1, false, 13);
|
||||
LockSupport.unpark(vThread1);
|
||||
vThread1.join();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,24 +25,29 @@
|
||||
* @test id=default
|
||||
* @bug 8312498
|
||||
* @summary Basic test for JVMTI GetThreadState with virtual threads
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native GetThreadStateTest
|
||||
* @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=no-vmcontinuations
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest
|
||||
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations --enable-native-access=ALL-UNNAMED GetThreadStateTest
|
||||
*/
|
||||
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class GetThreadStateTest {
|
||||
@ -51,6 +56,11 @@ class GetThreadStateTest {
|
||||
static void setup() {
|
||||
System.loadLibrary("GetThreadStateTest");
|
||||
init();
|
||||
|
||||
// need >=2 carriers for testing pinning when main thread is a virtual thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,21 +115,29 @@ class GetThreadStateTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test state of thread waiting to enter a monitor.
|
||||
* Test state of thread waiting to enter a monitor when pinned and not pinned.
|
||||
*/
|
||||
@Test
|
||||
void testMonitorEnter() throws Exception {
|
||||
var started = new AtomicBoolean();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testMonitorEnter(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
Object lock = new Object();
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.set(true);
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
synchronized (lock) { }
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
synchronized (lock) { }
|
||||
}
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
// start thread and wait for it to start execution
|
||||
thread.start();
|
||||
awaitTrue(started);
|
||||
awaitTrue(ready);
|
||||
|
||||
// thread should block on monitor enter
|
||||
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
|
||||
@ -135,23 +153,31 @@ class GetThreadStateTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test state of thread waiting in Object.wait().
|
||||
* Test state of thread waiting in Object.wait() when pinned and not pinned.
|
||||
*/
|
||||
@Test
|
||||
void testObjectWait() throws Exception {
|
||||
var started = new AtomicBoolean();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWait(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
Object lock = new Object();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
started.set(true);
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start execution
|
||||
awaitTrue(started);
|
||||
awaitTrue(ready);
|
||||
|
||||
// thread should wait
|
||||
int expected = JVMTI_THREAD_STATE_ALIVE |
|
||||
@ -177,23 +203,33 @@ class GetThreadStateTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test state of thread waiting in Object.wait(millis).
|
||||
* Test state of thread waiting in Object.wait(millis) when pinned and not pinned.
|
||||
*/
|
||||
@Test
|
||||
void testObjectWaitMillis() throws Exception {
|
||||
var started = new AtomicBoolean();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitMillis(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
Object lock = new Object();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
started.set(true);
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start execution
|
||||
awaitTrue(started);
|
||||
awaitTrue(ready);
|
||||
|
||||
// thread should wait
|
||||
int expected = JVMTI_THREAD_STATE_ALIVE |
|
||||
@ -219,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
|
||||
void testPark() throws Exception {
|
||||
var started = new AtomicBoolean();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testPark(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.set(true);
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start execution
|
||||
awaitTrue(started);
|
||||
awaitTrue(ready);
|
||||
|
||||
// thread should park
|
||||
int expected = JVMTI_THREAD_STATE_ALIVE |
|
||||
@ -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
|
||||
void testParkNanos() throws Exception {
|
||||
var started = new AtomicBoolean();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testParkNanos(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.set(true);
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start execution
|
||||
awaitTrue(started);
|
||||
|
||||
// thread should park
|
||||
int expected = JVMTI_THREAD_STATE_ALIVE |
|
||||
JVMTI_THREAD_STATE_WAITING |
|
||||
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT |
|
||||
JVMTI_THREAD_STATE_PARKED;
|
||||
await(thread, expected);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test state of thread parked with LockSupport.park while holding a monitor.
|
||||
*/
|
||||
@Test
|
||||
void testParkWhenPinned() throws Exception {
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
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);
|
||||
} else {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start execution
|
||||
awaitTrue(started);
|
||||
awaitTrue(ready);
|
||||
|
||||
// thread should park
|
||||
int expected = JVMTI_THREAD_STATE_ALIVE |
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,47 +28,22 @@
|
||||
* @requires vm.continuations
|
||||
* @requires vm.jvmti
|
||||
* @requires vm.compMode != "Xcomp"
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native
|
||||
* -Djdk.virtualThreadScheduler.parallelism=9
|
||||
* -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach
|
||||
*/
|
||||
|
||||
import com.sun.tools.attach.VirtualMachine;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/*
|
||||
* The test uses custom implementation of the CountDownLatch class.
|
||||
* The reason is we want the state of tested thread to be predictable.
|
||||
* With java.util.concurrent.CountDownLatch it is not clear what thread state is expected.
|
||||
*/
|
||||
class CountDownLatch {
|
||||
private int count = 0;
|
||||
|
||||
CountDownLatch(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public synchronized void countDown() {
|
||||
count--;
|
||||
notify();
|
||||
}
|
||||
|
||||
public synchronized void await() throws InterruptedException {
|
||||
while (count > 0) {
|
||||
wait(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class VThreadEventTest {
|
||||
static final int TCNT1 = 10;
|
||||
static final int TCNT2 = 4;
|
||||
static final int TCNT3 = 4;
|
||||
static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3;
|
||||
static final int PARKED_THREAD_COUNT = 4;
|
||||
static final int SPINNING_THREAD_COUNT = 4;
|
||||
|
||||
private static void log(String msg) { System.out.println(msg); }
|
||||
|
||||
@ -77,128 +52,96 @@ public class VThreadEventTest {
|
||||
private static native int threadUnmountCount();
|
||||
|
||||
private static volatile boolean attached;
|
||||
private static boolean failed;
|
||||
private static List<Thread> test1Threads = new ArrayList(TCNT1);
|
||||
|
||||
private static CountDownLatch ready0 = new CountDownLatch(THREAD_CNT);
|
||||
private static CountDownLatch ready1 = new CountDownLatch(TCNT1);
|
||||
private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT);
|
||||
private static CountDownLatch mready = new CountDownLatch(1);
|
||||
|
||||
private static void await(CountDownLatch dumpedLatch) {
|
||||
try {
|
||||
dumpedLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
// called by agent when it is initialized and has enabled events
|
||||
static void agentStarted() {
|
||||
attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The test1 vthreads are kept unmounted until interrupted after agent attach.
|
||||
static final Runnable test1 = () -> {
|
||||
synchronized (test1Threads) {
|
||||
test1Threads.add(Thread.currentThread());
|
||||
}
|
||||
log("test1 vthread started");
|
||||
ready0.countDown();
|
||||
await(mready);
|
||||
ready1.countDown(); // to guaranty state is not State.WAITING after await(mready)
|
||||
try {
|
||||
Thread.sleep(20000); // big timeout to keep unmounted until interrupted
|
||||
} catch (InterruptedException ex) {
|
||||
// it is expected, ignore
|
||||
}
|
||||
ready2.countDown();
|
||||
};
|
||||
|
||||
// The test2 vthreads are kept mounted until agent attach.
|
||||
static final Runnable test2 = () -> {
|
||||
log("test2 vthread started");
|
||||
ready0.countDown();
|
||||
await(mready);
|
||||
while (!attached) {
|
||||
// keep mounted
|
||||
}
|
||||
ready2.countDown();
|
||||
};
|
||||
|
||||
// The test3 vthreads are kept mounted until agent attach.
|
||||
static final Runnable test3 = () -> {
|
||||
log("test3 vthread started");
|
||||
ready0.countDown();
|
||||
await(mready);
|
||||
while (!attached) {
|
||||
// keep mounted
|
||||
}
|
||||
LockSupport.parkNanos(10_000_000L); // will cause extra mount and unmount
|
||||
ready2.countDown();
|
||||
};
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (Runtime.getRuntime().availableProcessors() < 8) {
|
||||
log("WARNING: test expects at least 8 processors.");
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
System.out.println("Skipping test as current thread is a virtual thread");
|
||||
return;
|
||||
}
|
||||
try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
for (int i = 0; i < TCNT1; i++) {
|
||||
executorService.execute(test1);
|
||||
}
|
||||
for (int i = 0; i < TCNT2; i++) {
|
||||
executorService.execute(test2);
|
||||
}
|
||||
for (int i = 0; i < TCNT3; i++) {
|
||||
executorService.execute(test3);
|
||||
}
|
||||
await(ready0);
|
||||
mready.countDown();
|
||||
await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1()
|
||||
// wait for test1 threads to reach TIMED_WAITING state in sleep()
|
||||
for (Thread t : test1Threads) {
|
||||
Thread.State state = t.getState();
|
||||
log("DBG: state: " + state);
|
||||
while (state != Thread.State.TIMED_WAITING) {
|
||||
VThreadRunner.ensureParallelism(SPINNING_THREAD_COUNT+1);
|
||||
|
||||
// start threads that park (unmount)
|
||||
var threads1 = new ArrayList<Thread>();
|
||||
for (int i = 0; i < PARKED_THREAD_COUNT; i++) {
|
||||
var started = new AtomicBoolean();
|
||||
var thread = Thread.startVirtualThread(() -> {
|
||||
started.set(true);
|
||||
LockSupport.park();
|
||||
});
|
||||
|
||||
// wait for thread to start execution + park
|
||||
while (!started.get()) {
|
||||
Thread.sleep(10);
|
||||
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()));
|
||||
vm.loadAgentLibrary("VThreadEventTest");
|
||||
Thread.sleep(200); // to allow the agent to get ready
|
||||
|
||||
attached = true;
|
||||
for (Thread t : test1Threads) {
|
||||
t.interrupt();
|
||||
// wait for agent to start
|
||||
while (!attached) {
|
||||
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++) {
|
||||
Thread.sleep(100);
|
||||
if (sleepNo % 100 == 0) { // 10 sec period of waiting
|
||||
log("main: waited seconds: " + sleepNo/10);
|
||||
|
||||
// wait for all threads to terminate
|
||||
for (Thread thread : threads1) {
|
||||
thread.join();
|
||||
}
|
||||
for (Thread thread : threads2) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
int threadEndCnt = threadEndCount();
|
||||
int threadMountCnt = threadMountCount();
|
||||
int threadUnmountCnt = threadUnmountCount();
|
||||
int threadEndExp = THREAD_CNT;
|
||||
int threadMountExp = THREAD_CNT - TCNT2;
|
||||
int threadUnmountExp = THREAD_CNT + TCNT3;
|
||||
|
||||
log("ThreadEnd cnt: " + threadEndCnt + " (expected: " + threadEndExp + ")");
|
||||
log("ThreadMount cnt: " + threadMountCnt + " (expected: " + threadMountExp + ")");
|
||||
log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")");
|
||||
int threadCount = PARKED_THREAD_COUNT + SPINNING_THREAD_COUNT;
|
||||
log("VirtualThreadEnd events: " + threadEndCnt + ", expected: " + threadCount);
|
||||
log("VirtualThreadMount events: " + threadMountCnt + ", expected: " + PARKED_THREAD_COUNT);
|
||||
log("VirtualThreadUnmount events: " + threadUnmountCnt + ", expected: " + threadCount);
|
||||
|
||||
if (threadEndCnt != threadEndExp) {
|
||||
log("FAILED: unexpected count of ThreadEnd events");
|
||||
boolean failed = false;
|
||||
if (threadEndCnt != threadCount) {
|
||||
log("FAILED: unexpected count of VirtualThreadEnd events");
|
||||
failed = true;
|
||||
}
|
||||
if (threadMountCnt != threadMountExp) {
|
||||
log("FAILED: unexpected count of ThreadMount events");
|
||||
if (threadMountCnt != PARKED_THREAD_COUNT) {
|
||||
log("FAILED: unexpected count of VirtualThreadMount events");
|
||||
failed = true;
|
||||
}
|
||||
if (threadUnmountCnt != threadUnmountExp) {
|
||||
log("FAILED: unexpected count of ThreadUnmount events");
|
||||
if (threadUnmountCnt != threadCount) {
|
||||
log("FAILED: unexpected count of VirtualThreadUnmount events");
|
||||
failed = true;
|
||||
}
|
||||
if (failed) {
|
||||
@ -206,5 +149,14 @@ public class VThreadEventTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assert state != Thread.State.TERMINATED : "Thread has terminated";
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,11 @@ Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
|
||||
jvmtiEventCallbacks callbacks;
|
||||
jvmtiCapabilities caps;
|
||||
jvmtiError err;
|
||||
JNIEnv *env;
|
||||
jsize nVMs;
|
||||
jint res;
|
||||
jclass clazz;
|
||||
jmethodID mid;
|
||||
|
||||
LOG("Agent_OnAttach started\n");
|
||||
if (vm->GetEnv(reinterpret_cast<void **>(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) {
|
||||
@ -97,6 +102,41 @@ Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
|
||||
check_jvmti_error(err, "SetEventNotificationMode for VirtualThreadUnmount");
|
||||
|
||||
LOG("vthread events enabled\n");
|
||||
|
||||
// call VThreadEventTest.agentStarted to notify test that agent has started
|
||||
|
||||
res = JNI_GetCreatedJavaVMs(&vm, 1, &nVMs);
|
||||
if (res != JNI_OK) {
|
||||
LOG("JNI_GetCreatedJavaVMs failed: %d\n", res);
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
res = vm->GetEnv((void **) &env, JNI_VERSION_21);
|
||||
if (res != JNI_OK) {
|
||||
LOG("GetEnv failed: %d\n", res);
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
clazz = env->FindClass("VThreadEventTest");
|
||||
if (clazz == NULL) {
|
||||
LOG("FindClass failed\n");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
mid = env->GetStaticMethodID(clazz, "agentStarted", "()V");
|
||||
if (mid == NULL) {
|
||||
LOG("GetStaticMethodID failed\n");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
env->CallStaticVoidMethod(clazz, mid);
|
||||
if (env->ExceptionOccurred()) {
|
||||
LOG("CallStaticVoidMethod failed\n");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
LOG("Agent_OnAttach done\n");
|
||||
|
||||
return JVMTI_ERROR_NONE;
|
||||
}
|
||||
|
||||
|
243
test/jdk/java/lang/Thread/virtual/CarrierThreadInfo.java
Normal file
243
test/jdk/java/lang/Thread/virtual/CarrierThreadInfo.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -26,6 +26,7 @@
|
||||
* @summary Test virtual threads using a custom scheduler
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit CustomScheduler
|
||||
*/
|
||||
|
||||
@ -35,9 +36,12 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
@ -65,10 +69,13 @@ class CustomScheduler {
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler1() throws Exception {
|
||||
AtomicReference<Executor> ref = new AtomicReference<>();
|
||||
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
|
||||
ref.set(scheduler(Thread.currentThread()));
|
||||
}).join();
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
Thread thread = factory.newThread(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
}
|
||||
|
||||
@ -77,17 +84,7 @@ class CustomScheduler {
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler2() throws Exception {
|
||||
AtomicReference<Executor> ref = new AtomicReference<>();
|
||||
Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
|
||||
ref.set(scheduler(Thread.currentThread()));
|
||||
}).join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
VThreadRunner.run(this::testCustomScheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,16 +93,19 @@ class CustomScheduler {
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler3() throws Exception {
|
||||
AtomicReference<Executor> ref = new AtomicReference<>();
|
||||
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
Thread thread = factory.newThread(() -> {
|
||||
try {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
ref.set(scheduler(Thread.currentThread()));
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
}).join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).join();
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
}
|
||||
|
||||
@ -115,16 +115,22 @@ class CustomScheduler {
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler4() throws Exception {
|
||||
AtomicReference<Executor> ref = new AtomicReference<>();
|
||||
ThreadBuilders.virtualThreadBuilder(scheduler1).start(() -> {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory1 = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
ThreadFactory factory2 = VThreadScheduler.virtualThreadFactory(scheduler2);
|
||||
Thread thread1 = factory1.newThread(() -> {
|
||||
try {
|
||||
ThreadBuilders.virtualThreadBuilder(scheduler2).start(() -> {
|
||||
ref.set(scheduler(Thread.currentThread()));
|
||||
}).join();
|
||||
Thread thread2 = factory2.newThread(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
});
|
||||
thread2.start();
|
||||
thread2.join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).join();
|
||||
});
|
||||
thread1.start();
|
||||
thread1.join();
|
||||
assertTrue(ref.get() == scheduler2);
|
||||
}
|
||||
|
||||
@ -149,8 +155,9 @@ class CustomScheduler {
|
||||
}
|
||||
assertTrue(exc.get() instanceof WrongThreadException);
|
||||
};
|
||||
|
||||
ThreadBuilders.virtualThreadBuilder(scheduler).start(LockSupport::park);
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,11 +169,12 @@ class CustomScheduler {
|
||||
Thread carrier = Thread.currentThread();
|
||||
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
|
||||
try {
|
||||
var builder = ThreadBuilders.virtualThreadBuilder(Runnable::run);
|
||||
Thread vthread = builder.start(() -> {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
Thread.yield();
|
||||
});
|
||||
vthread.start();
|
||||
assertTrue(vthread.isInterrupted());
|
||||
assertFalse(carrier.isInterrupted());
|
||||
} finally {
|
||||
@ -183,10 +191,11 @@ class CustomScheduler {
|
||||
Thread carrier = Thread.currentThread();
|
||||
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
|
||||
try {
|
||||
var builder = ThreadBuilders.virtualThreadBuilder(Runnable::run);
|
||||
Thread vthread = builder.start(() -> {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
});
|
||||
vthread.start();
|
||||
assertTrue(vthread.isInterrupted());
|
||||
assertFalse(carrier.isInterrupted());
|
||||
} finally {
|
||||
@ -204,11 +213,13 @@ class CustomScheduler {
|
||||
Thread.currentThread().interrupt();
|
||||
task.run();
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
try {
|
||||
AtomicBoolean interrupted = new AtomicBoolean();
|
||||
Thread vthread = ThreadBuilders.virtualThreadBuilder(scheduler).start(() -> {
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
interrupted.set(Thread.currentThread().isInterrupted());
|
||||
});
|
||||
vthread.start();
|
||||
assertFalse(vthread.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
@ -216,18 +227,60 @@ class CustomScheduler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scheduler for the given virtual thread.
|
||||
* Test custom scheduler throwing OOME when starting a thread.
|
||||
*/
|
||||
private static Executor scheduler(Thread thread) {
|
||||
if (!thread.isVirtual())
|
||||
throw new IllegalArgumentException("Not a virtual thread");
|
||||
try {
|
||||
Field scheduler = Class.forName("java.lang.VirtualThread")
|
||||
.getDeclaredField("scheduler");
|
||||
scheduler.setAccessible(true);
|
||||
return (Executor) scheduler.get(thread);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
@Test
|
||||
void testThreadStartOOME() throws Exception {
|
||||
Executor scheduler = task -> {
|
||||
System.err.println("OutOfMemoryError");
|
||||
throw new OutOfMemoryError();
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(() -> { });
|
||||
assertThrows(OutOfMemoryError.class, thread::start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom scheduler throwing OOME when unparking a thread.
|
||||
*/
|
||||
@Test
|
||||
void testThreadUnparkOOME() throws Exception {
|
||||
try (ExecutorService executor = Executors.newFixedThreadPool(1)) {
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Executor scheduler = task -> {
|
||||
switch (counter.getAndIncrement()) {
|
||||
case 0 -> executor.execute(task); // Thread.start
|
||||
case 1, 2 -> { // unpark attempt 1+2
|
||||
System.err.println("OutOfMemoryError");
|
||||
throw new OutOfMemoryError();
|
||||
}
|
||||
default -> executor.execute(task);
|
||||
}
|
||||
executor.execute(task);
|
||||
};
|
||||
|
||||
// start thread and wait for it to park
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
var thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// unpark thread, this should retry until OOME is not thrown
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -40,9 +40,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ -52,15 +50,25 @@ import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class JfrEvents {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
int minParallelism = 2;
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
minParallelism++;
|
||||
}
|
||||
VThreadRunner.ensureParallelism(minParallelism);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
|
||||
*/
|
||||
@ -93,100 +101,42 @@ class JfrEvents {
|
||||
}
|
||||
|
||||
/**
|
||||
* Arguments for testVirtualThreadPinned to test jdk.VirtualThreadPinned event.
|
||||
* [0] label/description
|
||||
* [1] the operation to park/wait
|
||||
* [2] the Thread.State when parked/waiting
|
||||
* [3] the action to unpark/notify the thread
|
||||
*/
|
||||
static Stream<Arguments> pinnedCases() {
|
||||
Object lock = new Object();
|
||||
|
||||
// park with native frame on stack
|
||||
var finish1 = new AtomicBoolean();
|
||||
var parkWhenPinned = Arguments.of(
|
||||
"LockSupport.park when pinned",
|
||||
(ThrowingRunnable<Exception>) () -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
while (!finish1.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
},
|
||||
Thread.State.WAITING,
|
||||
(Consumer<Thread>) t -> {
|
||||
finish1.set(true);
|
||||
LockSupport.unpark(t);
|
||||
}
|
||||
);
|
||||
|
||||
// timed park with native frame on stack
|
||||
var finish2 = new AtomicBoolean();
|
||||
var timedParkWhenPinned = Arguments.of(
|
||||
"LockSupport.parkNanos when pinned",
|
||||
(ThrowingRunnable<Exception>) () -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
while (!finish2.get()) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
},
|
||||
Thread.State.TIMED_WAITING,
|
||||
(Consumer<Thread>) t -> {
|
||||
finish2.set(true);
|
||||
LockSupport.unpark(t);
|
||||
}
|
||||
);
|
||||
|
||||
return Stream.of(parkWhenPinned, timedParkWhenPinned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event.
|
||||
* Test jdk.VirtualThreadPinned event when parking while pinned.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("pinnedCases")
|
||||
void testVirtualThreadPinned(String label,
|
||||
ThrowingRunnable<Exception> parker,
|
||||
Thread.State expectedState,
|
||||
Consumer<Thread> unparker) throws Exception {
|
||||
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testParkWhenPinned(boolean timed) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
|
||||
recording.start();
|
||||
try {
|
||||
var exception = new AtomicReference<Throwable>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
parker.run();
|
||||
} catch (Throwable e) {
|
||||
exception.set(e);
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to park/wait
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, thread.toString());
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
} finally {
|
||||
unparker.accept(thread);
|
||||
thread.join();
|
||||
assertNull(exception.get());
|
||||
}
|
||||
// wait for thread to start and park
|
||||
awaitTrue(started);
|
||||
await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
Map<String, Integer> events = sumEvents(recording);
|
||||
System.err.println(events);
|
||||
|
||||
// should have at least one pinned event
|
||||
int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0);
|
||||
assertTrue(pinnedCount >= 1, "Expected one or more events");
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,16 +153,14 @@ class JfrEvents {
|
||||
Executor scheduler = task -> pool.execute(task);
|
||||
|
||||
// create virtual thread that uses custom scheduler
|
||||
ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler).factory();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
// start a thread
|
||||
Thread thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
|
||||
// wait for thread to park
|
||||
while (thread.getState() != Thread.State.WAITING) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// shutdown scheduler
|
||||
pool.shutdown();
|
||||
@ -232,14 +180,23 @@ class JfrEvents {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
Map<String, Integer> events = sumEvents(recording);
|
||||
System.err.println(events);
|
||||
|
||||
int count = events.getOrDefault("jdk.VirtualThreadSubmitFailed", 0);
|
||||
assertEquals(2, count);
|
||||
List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
|
||||
System.err.println(submitFailedEvents);
|
||||
assertTrue(submitFailedEvents.size() == 2, "Expected two events");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events in the given recording with the given name.
|
||||
*/
|
||||
private static List<RecordedEvent> find(Recording recording, String name) throws IOException {
|
||||
Path recordingFile = recordingFile(recording);
|
||||
return RecordingFile.readAllEvents(recordingFile)
|
||||
.stream()
|
||||
.filter(e -> e.getEventType().getName().equals(name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the events from the recording and return a map of event name to count.
|
||||
*/
|
||||
@ -264,4 +221,38 @@ class JfrEvents {
|
||||
}
|
||||
return recordingFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread.
|
||||
*/
|
||||
private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException {
|
||||
List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned");
|
||||
assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording");
|
||||
System.err.println(pinnedEvents);
|
||||
|
||||
long tid = thread.threadId();
|
||||
assertTrue(pinnedEvents.stream()
|
||||
.anyMatch(e -> e.getThread().getJavaThreadId() == tid),
|
||||
"jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given boolean to be set to true.
|
||||
*/
|
||||
private void awaitTrue(AtomicBoolean b) throws InterruptedException {
|
||||
while (!b.get()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
410
test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
Normal file
410
test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -21,41 +21,78 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads using Object.wait/notifyAll
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit MonitorWaitNotify
|
||||
* @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class MonitorWaitNotify {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for testing pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread waits, notified by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testWaitNotify1() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testWaitNotify1(boolean pinned) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new Semaphore(0);
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
ready.release();
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
// thread invokes notify
|
||||
ready.acquire();
|
||||
awaitTrue(ready);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
@ -66,15 +103,13 @@ class MonitorWaitNotify {
|
||||
@Test
|
||||
void testWaitNotify2() throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new Semaphore(0);
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
ready.acquireUninterruptibly();
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
});
|
||||
synchronized (lock) {
|
||||
ready.release();
|
||||
thread.start();
|
||||
lock.wait();
|
||||
}
|
||||
thread.join();
|
||||
@ -83,89 +118,447 @@ class MonitorWaitNotify {
|
||||
/**
|
||||
* Test virtual thread waits, notified by another virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testWaitNotify3() throws Exception {
|
||||
// need at least two carrier threads due to pinning
|
||||
int previousParallelism = VThreadRunner.ensureParallelism(2);
|
||||
try {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testWaitNotify3(boolean pinned) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new Semaphore(0);
|
||||
var ready = new AtomicBoolean();
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
ready.release();
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
var thread2 = Thread.ofVirtual().start(() -> {
|
||||
ready.acquireUninterruptibly();
|
||||
try {
|
||||
awaitTrue(ready);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread1, Thread.State.BLOCKED);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notifyAll when there are no threads waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testNotifyBeforeWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
// no threads waiting
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
|
||||
// thread should wait
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
Thread.State expectedState = timeout > 0
|
||||
? Thread.State.TIMED_WAITING
|
||||
: Thread.State.WAITING;
|
||||
await(thread, expectedState);
|
||||
|
||||
// poll thread state again, it should still be waiting
|
||||
Thread.sleep(10);
|
||||
assertEquals(thread.getState(), expectedState);
|
||||
} finally {
|
||||
// restore
|
||||
VThreadRunner.setParallelism(previousParallelism);
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Test duration of timed Object.wait.
|
||||
*/
|
||||
@Test
|
||||
void testTimedWaitDuration1() throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
var durationRef = new AtomicReference<Long>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
long start = millisTime();
|
||||
lock.wait(2000);
|
||||
durationRef.set(millisTime() - start);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
thread.join();
|
||||
|
||||
long duration = durationRef.get();
|
||||
checkDuration(duration, 1900, 20_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 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
|
||||
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(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
fail();
|
||||
} catch (InterruptedException e) {
|
||||
// interrupt status should be cleared
|
||||
assertFalse(t.isInterrupted());
|
||||
Thread.currentThread().interrupt();
|
||||
if (timeout > 0) {
|
||||
assertThrows(InterruptedException.class, () -> lock.wait(timeout));
|
||||
} else {
|
||||
assertThrows(InterruptedException.class, lock::wait);
|
||||
}
|
||||
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
|
||||
void testWaitNotify5() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
Object lock = new Object();
|
||||
void testParkingPermitNotConsumed() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var completed = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
fail();
|
||||
} catch (InterruptedException e) {
|
||||
// interrupt status should be cleared
|
||||
assertFalse(t.isInterrupted());
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
LockSupport.park(); // should not park
|
||||
completed.set(true);
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// wakeup thread
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// thread should terminate
|
||||
vthread.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait does not make available the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotOffered() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var readyToPark = new CountDownLatch(1);
|
||||
var completed = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
readyToPark.countDown();
|
||||
LockSupport.park(); // should park
|
||||
completed.set(true);
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// wakeup thread
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// thread should park
|
||||
readyToPark.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
LockSupport.unpark(vthread);
|
||||
|
||||
// thread should terminate
|
||||
vthread.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that wait(long) throws IAE when timeout is negative.
|
||||
*/
|
||||
@Test
|
||||
void testIllegalArgumentException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object obj = new Object();
|
||||
synchronized (obj) {
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L));
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L));
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Runnable interruptTask = () -> {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
thread.interrupt();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@Test
|
||||
void testIllegalMonitorStateException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object obj = new Object();
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait());
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0));
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000));
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE));
|
||||
});
|
||||
}
|
||||
};
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,14 @@
|
||||
* @summary Test virtual thread park when scheduler is a fixed thread pool
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main ParkWithFixedThreadPool
|
||||
*/
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
public class ParkWithFixedThreadPool {
|
||||
public static void main(String[] args) throws Exception {
|
||||
@ -58,9 +60,7 @@ public class ParkWithFixedThreadPool {
|
||||
}
|
||||
};
|
||||
|
||||
ThreadFactory factory = ThreadBuilders.virtualThreadBuilder(scheduler)
|
||||
.name("vthread-", 0)
|
||||
.factory();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i] = factory.newThread(target);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -38,6 +38,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@ -142,11 +143,10 @@ class Reflection {
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic6() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Method parkMethod = Parker.class.getDeclaredMethod("park");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
ThreadFactory factory = builder.factory();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new CountDownLatch(1);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
@ -321,11 +321,10 @@ class Reflection {
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance6() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Constructor<?> ctor = Parker.class.getDeclaredConstructor();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
ThreadFactory factory = builder.factory();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new CountDownLatch(1);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
|
160
test/jdk/java/lang/Thread/virtual/StackFrames.java
Normal file
160
test/jdk/java/lang/Thread/virtual/StackFrames.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -61,13 +61,16 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Stream;
|
||||
import java.nio.channels.Selector;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
@ -78,9 +81,12 @@ class ThreadAPI {
|
||||
private static ScheduledExecutorService scheduler;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws Exception {
|
||||
static void setup() {
|
||||
ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
scheduler = Executors.newSingleThreadScheduledExecutor(factory);
|
||||
|
||||
// need >=2 carriers for testing pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@ -719,14 +725,7 @@ class ThreadAPI {
|
||||
*/
|
||||
@Test
|
||||
void testJoin34() throws Exception {
|
||||
// need at least two carrier threads due to pinning
|
||||
int previousParallelism = VThreadRunner.ensureParallelism(2);
|
||||
try {
|
||||
VThreadRunner.run(this::testJoin33);
|
||||
} finally {
|
||||
// restore
|
||||
VThreadRunner.setParallelism(previousParallelism);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1084,11 +1083,10 @@ class ThreadAPI {
|
||||
*/
|
||||
@Test
|
||||
void testYield1() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
var list = new CopyOnWriteArrayList<String>();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
ThreadFactory factory = builder.factory();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
var thread = factory.newThread(() -> {
|
||||
list.add("A");
|
||||
var child = factory.newThread(() -> {
|
||||
@ -1112,11 +1110,10 @@ class ThreadAPI {
|
||||
*/
|
||||
@Test
|
||||
void testYield2() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
var list = new CopyOnWriteArrayList<String>();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
ThreadFactory factory = builder.factory();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
var thread = factory.newThread(() -> {
|
||||
list.add("A");
|
||||
var child = factory.newThread(() -> {
|
||||
@ -1708,50 +1705,59 @@ class ThreadAPI {
|
||||
*/
|
||||
@Test
|
||||
void testGetState4() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
AtomicBoolean completed = new AtomicBoolean();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
Thread t1 = builder.start(() -> {
|
||||
Thread t2 = builder.unstarted(LockSupport::park);
|
||||
assertEquals(Thread.State.NEW, t2.getState());
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread1 = factory.newThread(() -> {
|
||||
Thread thread2 = factory.newThread(LockSupport::park);
|
||||
assertEquals(Thread.State.NEW, thread2.getState());
|
||||
|
||||
// start t2 to make it runnable
|
||||
t2.start();
|
||||
thread2.start();
|
||||
try {
|
||||
assertEquals(Thread.State.RUNNABLE, t2.getState());
|
||||
assertEquals(Thread.State.RUNNABLE, thread2.getState());
|
||||
|
||||
// yield to allow t2 to run and park
|
||||
Thread.yield();
|
||||
assertEquals(Thread.State.WAITING, t2.getState());
|
||||
assertEquals(Thread.State.WAITING, thread2.getState());
|
||||
} finally {
|
||||
// unpark t2 to make it runnable again
|
||||
LockSupport.unpark(t2);
|
||||
LockSupport.unpark(thread2);
|
||||
}
|
||||
|
||||
// t2 should be runnable (not mounted)
|
||||
assertEquals(Thread.State.RUNNABLE, t2.getState());
|
||||
assertEquals(Thread.State.RUNNABLE, thread2.getState());
|
||||
|
||||
completed.set(true);
|
||||
});
|
||||
t1.join();
|
||||
thread1.start();
|
||||
thread1.join();
|
||||
}
|
||||
assertTrue(completed.get() == true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getState when thread is waiting to enter a monitor.
|
||||
* Test Thread::getState when thread is blocked waiting to enter a monitor.
|
||||
*/
|
||||
@Test
|
||||
void testGetState5() throws Exception {
|
||||
var started = new CountDownLatch(1);
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testGetState5(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
synchronized (lock) { }
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
synchronized (lock) { }
|
||||
}
|
||||
});
|
||||
synchronized (lock) {
|
||||
thread.start();
|
||||
started.await();
|
||||
awaitTrue(ready);
|
||||
|
||||
// wait for thread to block
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
@ -1762,16 +1768,35 @@ class ThreadAPI {
|
||||
/**
|
||||
* Test Thread::getState when thread is waiting in Object.wait.
|
||||
*/
|
||||
@Test
|
||||
void testGetState6() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testGetState6(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try { lock.wait(); } catch (InterruptedException e) { }
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to wait
|
||||
awaitTrue(ready);
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// notify, thread should block trying to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
} finally {
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
@ -1781,18 +1806,35 @@ class ThreadAPI {
|
||||
/**
|
||||
* Test Thread::getState when thread is waiting in Object.wait(millis).
|
||||
*/
|
||||
@Test
|
||||
void testGetState7() throws Exception {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testGetState7(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to wait
|
||||
// wait for thread to timed-wait
|
||||
awaitTrue(ready);
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
|
||||
// notify, thread should block trying to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
} finally {
|
||||
thread.interrupt();
|
||||
thread.join();
|
||||
@ -1933,7 +1975,7 @@ class ThreadAPI {
|
||||
* Test Thread::getStackTrace on unstarted thread.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace1() {
|
||||
void testGetStackTraceUnstarted() {
|
||||
var thread = Thread.ofVirtual().unstarted(() -> { });
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(stack.length == 0);
|
||||
@ -1943,136 +1985,224 @@ class ThreadAPI {
|
||||
* Test Thread::getStackTrace on thread that has been started but has not run.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace2() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
void testGetStackTraceStarted() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Executor scheduler = task -> { };
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
Thread thread = builder.start(() -> { });
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(() -> { });
|
||||
thread.start();
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(stack.length == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getStackTrace on running thread.
|
||||
* Test Thread::getStackTrace on thread that is runnable-mounted.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace3() throws Exception {
|
||||
var sel = Selector.open();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try { sel.select(); } catch (Exception e) { }
|
||||
});
|
||||
try {
|
||||
while (!contains(thread.getStackTrace(), "select")) {
|
||||
assertTrue(thread.isAlive());
|
||||
Thread.sleep(20);
|
||||
void testGetStackTraceRunnableMounted() throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
|
||||
class Foo {
|
||||
void spinUntilDone() {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Foo foo = new Foo();
|
||||
var thread = Thread.ofVirtual().start(foo::spinUntilDone);
|
||||
try {
|
||||
awaitTrue(ready);
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(contains(stack, Foo.class.getName() + ".spinUntilDone"));
|
||||
} finally {
|
||||
sel.close();
|
||||
done.set(true);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getStackTrace on thread waiting in Object.wait.
|
||||
* Test Thread::getStackTrace on thread that is runnable-unmounted.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace4() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ForkJoinPool pool = new ForkJoinPool(1)) {
|
||||
AtomicReference<Thread> ref = new AtomicReference<>();
|
||||
Executor scheduler = task -> {
|
||||
pool.submit(() -> {
|
||||
ref.set(Thread.currentThread());
|
||||
task.run();
|
||||
});
|
||||
};
|
||||
void testGetStackTraceRunnableUnmounted() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
Thread vthread = builder.start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (Exception e) { }
|
||||
// custom scheduler with one carrier thread
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
// start thread1 to park
|
||||
Thread thread1 = factory.newThread(LockSupport::park);
|
||||
thread1.start();
|
||||
await(thread1, Thread.State.WAITING);
|
||||
|
||||
// start thread2 to spin and pin the carrier thread
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
Thread thread2 = factory.newThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
thread2.start();
|
||||
awaitTrue(started);
|
||||
|
||||
// get carrier Thread
|
||||
Thread carrier;
|
||||
while ((carrier = ref.get()) == null) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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"));
|
||||
|
||||
} finally {
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getStackTrace on timed-parked thread.
|
||||
* Test Thread::getStackTrace on thread blocked on monitor enter.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace6() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
});
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
try {
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(contains(stack, "LockSupport.parkNanos"));
|
||||
} finally {
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testGetStackTraceBlocked(boolean pinned) throws Exception {
|
||||
class Foo {
|
||||
void enter() {
|
||||
synchronized (this) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getStackTrace on parked thread that is pinned.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace7() throws Exception {
|
||||
AtomicBoolean done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
Foo foo = new Foo();
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
foo.enter();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
foo.enter();
|
||||
}
|
||||
});
|
||||
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()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
await(thread, Thread.State.WAITING);
|
||||
try {
|
||||
// wait for thread to park
|
||||
awaitTrue(ready);
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(contains(stack, "LockSupport.park"));
|
||||
} finally {
|
||||
@ -2083,20 +2213,33 @@ class ThreadAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread::getStackTrace on timed-parked thread that is pinned.
|
||||
* Test Thread::getStackTrace when thread in timed-park.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace8() throws Exception {
|
||||
AtomicBoolean done = new AtomicBoolean();
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testGetStackTraceTimedPark(boolean pinned) throws Exception {
|
||||
var ready = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
if (pinned) {
|
||||
ready.set(true);
|
||||
VThreadPinner.runPinned(() -> {
|
||||
while (!done.get()) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
});
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
try {
|
||||
// wait for thread to park
|
||||
awaitTrue(ready);
|
||||
await(thread, Thread.State.TIMED_WAITING);
|
||||
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
assertTrue(contains(stack, "LockSupport.parkNanos"));
|
||||
} finally {
|
||||
@ -2110,7 +2253,7 @@ class ThreadAPI {
|
||||
* Test Thread::getStackTrace on terminated thread.
|
||||
*/
|
||||
@Test
|
||||
void testGetStackTrace9() throws Exception {
|
||||
void testGetStackTraceTerminated() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> { });
|
||||
thread.join();
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
@ -2133,7 +2276,7 @@ class ThreadAPI {
|
||||
*/
|
||||
@Test
|
||||
void testGetAllStackTraces2() throws Exception {
|
||||
assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ForkJoinPool pool = new ForkJoinPool(1)) {
|
||||
AtomicReference<Thread> ref = new AtomicReference<>();
|
||||
Executor scheduler = task -> {
|
||||
@ -2143,14 +2286,15 @@ class ThreadAPI {
|
||||
});
|
||||
};
|
||||
|
||||
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
|
||||
Thread vthread = builder.start(() -> {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
});
|
||||
vthread.start();
|
||||
|
||||
// get carrier Thread
|
||||
Thread carrier;
|
||||
@ -2168,8 +2312,9 @@ class ThreadAPI {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
vthread.join();
|
||||
|
||||
// get stack trace for the carrier thread
|
||||
// stack trace for the carrier thread
|
||||
StackTraceElement[] stackTrace = map.get(carrier);
|
||||
assertNotNull(stackTrace);
|
||||
assertTrue(contains(stackTrace, "java.util.concurrent.ForkJoinPool"));
|
||||
@ -2417,6 +2562,15 @@ class ThreadAPI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws Exception {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,7 +24,7 @@
|
||||
/**
|
||||
* @test
|
||||
* @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws
|
||||
* @modules java.base/jdk.internal.event
|
||||
* @modules java.base/java.lang:+open java.base/jdk.internal.event
|
||||
* @library /test/lib
|
||||
* @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java
|
||||
* @run junit/othervm --enable-native-access=ALL-UNNAMED VirtualThreadPinnedEventThrows
|
||||
@ -36,12 +36,22 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.internal.event.VirtualThreadPinnedEvent;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class VirtualThreadPinnedEventThrows {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for testing pinning when main thread is a virtual thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test parking when pinned and creating the VirtualThreadPinnedEvent fails with OOME.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,17 +25,18 @@
|
||||
* @test
|
||||
* @summary Stress parking with CompletableFuture timed get
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/othervm -Xmx1g TimedGet 100000
|
||||
* @run main/othervm -Xmx1g CompletableFutureTimedGet 100000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class TimedGet {
|
||||
public class CompletableFutureTimedGet {
|
||||
|
||||
static final String RESULT = "foo";
|
||||
|
||||
@ -77,30 +78,27 @@ public class TimedGet {
|
||||
|
||||
// wait for all threads to terminate
|
||||
long lastTimestamp = System.currentTimeMillis();
|
||||
int i = 0;
|
||||
while (i < threadCount) {
|
||||
Thread t = threads.get(i);
|
||||
boolean terminated;
|
||||
if (t.isAlive()) {
|
||||
terminated = t.join(Duration.ofMillis(500));
|
||||
boolean done;
|
||||
do {
|
||||
done = true;
|
||||
for (Thread t : threads) {
|
||||
if (!t.join(Duration.ofSeconds(1))) {
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
|
||||
// print trace message so the output tracks progress
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - lastTimestamp) > 500) {
|
||||
System.out.println(completed.get());
|
||||
if (done || ((currentTime - lastTimestamp) > 500)) {
|
||||
System.out.format("%s => completed %d of %d%n",
|
||||
Instant.now(), completed.get(), threadCount);
|
||||
lastTimestamp = currentTime;
|
||||
}
|
||||
} else {
|
||||
terminated = true;
|
||||
}
|
||||
if (terminated) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
} while (!done);
|
||||
|
||||
// all tasks should have completed successfully
|
||||
int completedCount = completed.get();
|
||||
System.out.println(completedCount);
|
||||
if (completedCount != threadCount) {
|
||||
throw new RuntimeException("completed = " + completedCount);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,30 +23,32 @@
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Stress test asynchronous Thread.getStackTrace
|
||||
* @summary Stress test asynchronous Thread.getStackTrace when parking
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @compile GetStackTraceALot.java ../ThreadBuilders.java
|
||||
* @run main GetStackTraceALot
|
||||
* @library /test/lib
|
||||
* @run main GetStackTraceALotWhenParking
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @compile GetStackTraceALot.java ../ThreadBuilders.java
|
||||
* @run main/timeout=300 GetStackTraceALot 1000
|
||||
* @library /test/lib
|
||||
* @run main/timeout=300 GetStackTraceALotWhenParking 1000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
public class GetStackTraceALot {
|
||||
public class GetStackTraceALotWhenParking {
|
||||
static class RoundRobinExecutor implements Executor, AutoCloseable {
|
||||
private final ExecutorService[] executors;
|
||||
private int next;
|
||||
@ -83,7 +85,9 @@ public class GetStackTraceALot {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
try (RoundRobinExecutor executor = new RoundRobinExecutor()) {
|
||||
Thread thread = ThreadBuilders.virtualThreadBuilder(executor).start(() -> {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(executor);
|
||||
|
||||
Thread thread = factory.newThread(() -> {
|
||||
while (count.incrementAndGet() < ITERATIONS) {
|
||||
long start = System.nanoTime();
|
||||
while ((System.nanoTime() - start) < SPIN_NANOS) {
|
||||
@ -92,6 +96,7 @@ public class GetStackTraceALot {
|
||||
LockSupport.parkNanos(500_000);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
long start = System.nanoTime();
|
||||
while (thread.isAlive()) {
|
||||
@ -99,7 +104,7 @@ public class GetStackTraceALot {
|
||||
// printStackTrace(stackTrace);
|
||||
Thread.sleep(5);
|
||||
if ((System.nanoTime() - start) > 500_000_000) {
|
||||
System.out.println(count.get());
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), count.get(), ITERATIONS);
|
||||
start = System.nanoTime();
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
* @requires vm.debug != true
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/othervm GetStackTraceALotWhenPinned 500000
|
||||
* @run main/othervm --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -36,7 +36,7 @@
|
||||
* @requires vm.debug == true
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=300 GetStackTraceALotWhenPinned 200000
|
||||
* @run main/othervm/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 200000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
@ -79,7 +79,7 @@ public class GetStackTraceALotWhenPinned {
|
||||
});
|
||||
|
||||
long lastTimestamp = System.currentTimeMillis();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
// wait for virtual thread to arrive
|
||||
barrier.await();
|
||||
|
||||
@ -87,8 +87,8 @@ public class GetStackTraceALotWhenPinned {
|
||||
LockSupport.unpark(thread);
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - lastTimestamp) > 500) {
|
||||
System.out.format("%s %d remaining ...%n", Instant.now(), (iterations - i));
|
||||
if (i == iterations || ((currentTime - lastTimestamp) > 500)) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
|
||||
lastTimestamp = currentTime;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -46,17 +46,17 @@ import jdk.test.lib.thread.VThreadPinner;
|
||||
public class PinALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = 1_000_000;
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
final int ITERATIONS = iterations;
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
while (count.incrementAndGet() < ITERATIONS) {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
LockSupport.parkNanos(1);
|
||||
}
|
||||
});
|
||||
@ -65,12 +65,12 @@ public class PinALot {
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get());
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != ITERATIONS) {
|
||||
throw new RuntimeException("count = " + countValue);
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -36,6 +36,7 @@
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -91,11 +92,12 @@ public class PingPong {
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = t1.join(Duration.ofMillis(500));
|
||||
if (terminated)
|
||||
if (terminated) {
|
||||
terminated = t2.join(Duration.ofMillis(500));
|
||||
System.out.format("%d %s%n", count1.get(), count2.get());
|
||||
}
|
||||
System.out.format("%s => T1 %d of %d, T2 %d of %d%n",
|
||||
Instant.now(), count1.get(), iterations, count2.get(), iterations);
|
||||
} while (!terminated);
|
||||
|
||||
}
|
||||
|
||||
interface Exchanger<E> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -69,7 +69,7 @@ public class Skynet {
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.format("Result: %d in %s ms%n", sum, (end-start));
|
||||
if (sum != expected)
|
||||
throw new AssertionError("unexpected result!");
|
||||
throw new RuntimeException("Expected " + expected);
|
||||
}
|
||||
|
||||
static void skynet(Channel<Long> result, int num, int size, int div) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -41,16 +41,16 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public class SleepALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = 1_000_000;
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
final int ITERATIONS = iterations;
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
while (count.incrementAndGet() < ITERATIONS) {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
try {
|
||||
Thread.sleep(Duration.ofNanos(100));
|
||||
} catch (InterruptedException ignore) { }
|
||||
@ -60,12 +60,12 @@ public class SleepALot {
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get());
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != ITERATIONS) {
|
||||
throw new RuntimeException("count = " + countValue);
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
129
test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java
Normal file
129
test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java
Normal 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);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,7 +25,7 @@
|
||||
* @test
|
||||
* @summary Stress test Thread.yield
|
||||
* @requires vm.debug != true
|
||||
* @run main YieldALot 350000
|
||||
* @run main YieldALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -35,34 +35,35 @@
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class YieldALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = 1_000_000;
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
final int ITERATIONS = iterations;
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
while (count.incrementAndGet() < ITERATIONS) {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
Thread.yield();
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofMillis(500));
|
||||
System.out.println(count.get());
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != ITERATIONS) {
|
||||
throw new RuntimeException("count = " + countValue);
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -41,19 +41,20 @@
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.channels.Selector;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
@ -195,7 +196,7 @@ public class VirtualThreads {
|
||||
*/
|
||||
@Test
|
||||
void testGetThreadInfoCarrierThread() throws Exception {
|
||||
assumeTrue(supportsCustomScheduler(), "No support for custom schedulers");
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService pool = Executors.newFixedThreadPool(1)) {
|
||||
var carrierRef = new AtomicReference<Thread>();
|
||||
Executor scheduler = (task) -> {
|
||||
@ -204,19 +205,30 @@ public class VirtualThreads {
|
||||
task.run();
|
||||
});
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
// start virtual thread so carrier Thread can be captured
|
||||
virtualThreadBuilder(scheduler).start(() -> { }).join();
|
||||
Thread thread = factory.newThread(() -> { });
|
||||
thread.start();
|
||||
thread.join();
|
||||
Thread carrier = carrierRef.get();
|
||||
assertTrue(carrier != null && !carrier.isVirtual());
|
||||
|
||||
try (Selector sel = Selector.open()) {
|
||||
// start virtual thread that blocks in a native method
|
||||
virtualThreadBuilder(scheduler).start(() -> {
|
||||
String selClassName = sel.getClass().getName();
|
||||
|
||||
// start virtual thread that blocks while pinned
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
try {
|
||||
sel.select();
|
||||
VThreadPinner.runPinned(sel::select);
|
||||
} catch (Exception e) { }
|
||||
});
|
||||
vthread.start();
|
||||
|
||||
// wait for virtual thread to block in select
|
||||
while (!contains(vthread.getStackTrace(), selClassName)) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
// invoke getThreadInfo get and check the stack trace
|
||||
long tid = carrier.getId();
|
||||
@ -225,7 +237,7 @@ public class VirtualThreads {
|
||||
// should only see carrier frames
|
||||
StackTraceElement[] stack = info.getStackTrace();
|
||||
assertTrue(contains(stack, "java.util.concurrent.ThreadPoolExecutor"));
|
||||
assertFalse(contains(stack, "java.nio.channels.Selector"));
|
||||
assertFalse(contains(stack, selClassName));
|
||||
|
||||
// carrier should not be holding any monitors
|
||||
assertEquals(0, info.getLockedMonitors().length);
|
||||
@ -351,40 +363,4 @@ public class VirtualThreads {
|
||||
.map(StackTraceElement::getClassName)
|
||||
.anyMatch(className::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a builder to create virtual threads that use the given scheduler.
|
||||
* @throws UnsupportedOperationException if there is no support for custom schedulers
|
||||
*/
|
||||
private static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
|
||||
Thread.Builder.OfVirtual builder = Thread.ofVirtual();
|
||||
try {
|
||||
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
|
||||
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
|
||||
ctor.setAccessible(true);
|
||||
return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof RuntimeException re) {
|
||||
throw re;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if custom schedulers are supported.
|
||||
*/
|
||||
private static boolean supportsCustomScheduler() {
|
||||
try (var pool = Executors.newCachedThreadPool()) {
|
||||
try {
|
||||
virtualThreadBuilder(pool);
|
||||
return true;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -426,7 +426,7 @@ class ThreadFlockTest {
|
||||
long startMillis = millisTime();
|
||||
boolean done = flock.awaitAll(Duration.ofSeconds(30));
|
||||
assertTrue(done);
|
||||
checkDuration(startMillis, 1900, 4000);
|
||||
checkDuration(startMillis, 1900, 20_000);
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,7 +453,7 @@ class ThreadFlockTest {
|
||||
flock.awaitAll(Duration.ofSeconds(2));
|
||||
fail("awaitAll did not throw");
|
||||
} catch (TimeoutException e) {
|
||||
checkDuration(startMillis, 1900, 4000);
|
||||
checkDuration(startMillis, 1900, 20_000);
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -21,28 +21,50 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.test.lib.thread;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* Helper class for creating Thread buidlers.
|
||||
* Helper class to allow tests run virtual threads with a custom scheduler.
|
||||
*
|
||||
* Tests using this class need to open java.base/java.lang.
|
||||
*/
|
||||
class ThreadBuilders {
|
||||
private ThreadBuilders() { }
|
||||
public class VThreadScheduler {
|
||||
private VThreadScheduler() { }
|
||||
|
||||
private static final Constructor<?> VTBUILDER_CTOR;
|
||||
static {
|
||||
/**
|
||||
* Returns the scheduler for the given virtual thread.
|
||||
*/
|
||||
public static Executor scheduler(Thread thread) {
|
||||
if (!thread.isVirtual())
|
||||
throw new IllegalArgumentException("Not a virtual thread");
|
||||
try {
|
||||
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
|
||||
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
|
||||
ctor.setAccessible(true);
|
||||
VTBUILDER_CTOR = ctor;
|
||||
Field scheduler = Class.forName("java.lang.VirtualThread")
|
||||
.getDeclaredField("scheduler");
|
||||
scheduler.setAccessible(true);
|
||||
return (Executor) scheduler.get(thread);
|
||||
} catch (Exception e) {
|
||||
throw new InternalError(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if custom schedulers are supported.
|
||||
*/
|
||||
public static boolean supportsCustomScheduler() {
|
||||
try (var pool = Executors.newCachedThreadPool()) {
|
||||
try {
|
||||
virtualThreadBuilder(pool);
|
||||
return true;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,9 +72,12 @@ class ThreadBuilders {
|
||||
* Returns a builder to create virtual threads that use the given scheduler.
|
||||
* @throws UnsupportedOperationException if custom schedulers are not supported
|
||||
*/
|
||||
static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
|
||||
public static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) {
|
||||
try {
|
||||
return (Thread.Builder.OfVirtual) VTBUILDER_CTOR.newInstance(scheduler);
|
||||
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
|
||||
Constructor<?> ctor = clazz.getDeclaredConstructor(Executor.class);
|
||||
ctor.setAccessible(true);
|
||||
return (Thread.Builder.OfVirtual) ctor.newInstance(scheduler);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof RuntimeException re) {
|
||||
@ -65,16 +90,10 @@ class ThreadBuilders {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if custom schedulers are supported.
|
||||
* Returns a ThreadFactory to create virtual threads that use the given scheduler.
|
||||
* @throws UnsupportedOperationException if custom schedulers are not supported
|
||||
*/
|
||||
static boolean supportsCustomScheduler() {
|
||||
try (var pool = Executors.newCachedThreadPool()) {
|
||||
try {
|
||||
virtualThreadBuilder(pool);
|
||||
return true;
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static ThreadFactory virtualThreadFactory(Executor scheduler) {
|
||||
return virtualThreadBuilder(scheduler).factory();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user