diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 0c016202b8e..badab7aa6ef 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1986,23 +1986,26 @@ int java_lang_VirtualThread::state(oop vthread) { JavaThreadStatus java_lang_VirtualThread::map_state_to_thread_status(int state) { JavaThreadStatus status = JavaThreadStatus::NEW; - switch (state) { + switch (state & ~SUSPENDED) { case NEW : status = JavaThreadStatus::NEW; break; case STARTED : case RUNNABLE : - case RUNNABLE_SUSPENDED : case RUNNING : case PARKING : + case TIMED_PARKING: case YIELDING : status = JavaThreadStatus::RUNNABLE; break; case PARKED : - case PARKED_SUSPENDED : case PINNED : status = JavaThreadStatus::PARKED; break; + case TIMED_PARKED: + case TIMED_PINNED: + status = JavaThreadStatus::PARKED_TIMED; + break; case TERMINATED : status = JavaThreadStatus::TERMINATED; break; diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index ee4ab1e80ae..c4a457e142b 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -521,20 +521,21 @@ class java_lang_VirtualThread : AllStatic { JFR_ONLY(static int _jfr_epoch_offset;) public: enum { - NEW = 0, - STARTED = 1, - RUNNABLE = 2, - RUNNING = 3, - PARKING = 4, - PARKED = 5, - PINNED = 6, - YIELDING = 7, - TERMINATED = 99, + NEW = 0, + STARTED = 1, + RUNNABLE = 2, + RUNNING = 3, + PARKING = 4, + PARKED = 5, + PINNED = 6, + TIMED_PARKING = 7, + TIMED_PARKED = 8, + TIMED_PINNED = 9, + YIELDING = 10, + TERMINATED = 99, - // can be suspended from scheduling when unmounted - SUSPENDED = 1 << 8, - RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED), - PARKED_SUSPENDED = (PARKED | SUSPENDED) + // additional state bits + SUSPENDED = 1 << 8, // suspended when unmounted }; static void compute_offsets(); diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 92a5b9547dd..e3b94c4ef90 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -86,41 +86,52 @@ final class VirtualThread extends BaseVirtualThread { private volatile int state; /* - * Virtual thread state and transitions: + * Virtual thread state transitions: * - * NEW -> STARTED // Thread.start + * NEW -> STARTED // Thread.start, schedule to run * STARTED -> TERMINATED // failed to start * STARTED -> RUNNING // first run + * RUNNING -> TERMINATED // done * - * RUNNING -> PARKING // Thread attempts to park - * PARKING -> PARKED // cont.yield successful, thread is parked - * PARKING -> PINNED // cont.yield failed, thread is pinned + * RUNNING -> PARKING // Thread parking with LockSupport.park + * PARKING -> PARKED // cont.yield successful, parked indefinitely + * PARKING -> PINNED // cont.yield failed, parked indefinitely on carrier + * PARKED -> RUNNABLE // unparked, schedule to continue + * PINNED -> RUNNING // unparked, continue execution on same carrier * - * PARKED -> RUNNABLE // unpark or interrupted - * PINNED -> RUNNABLE // unpark or interrupted + * RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos + * TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked + * TIMED_PARKING -> TIMED_PINNED // cont.yield failed, timed-parked on carrier + * TIMED_PARKED -> RUNNABLE // unparked, schedule to continue + * TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier * * RUNNABLE -> RUNNING // continue execution - * + * RUNNING -> YIELDING // Thread.yield * YIELDING -> RUNNABLE // yield successful * YIELDING -> RUNNING // yield failed - * - * RUNNING -> TERMINATED // done */ private static final int NEW = 0; private static final int STARTED = 1; private static final int RUNNABLE = 2; // runnable-unmounted private static final int RUNNING = 3; // runnable-mounted + + // untimed parking private static final int PARKING = 4; private static final int PARKED = 5; // unmounted private static final int PINNED = 6; // mounted - private static final int YIELDING = 7; // Thread.yield + + // timed parking + private static final int TIMED_PARKING = 7; + private static final int TIMED_PARKED = 8; + private static final int TIMED_PINNED = 9; + + private static final int YIELDING = 10; // Thread.yield + private static final int TERMINATED = 99; // final state // can be suspended from scheduling when unmounted private static final int SUSPENDED = 1 << 8; - private static final int RUNNABLE_SUSPENDED = (RUNNABLE | SUSPENDED); - private static final int PARKED_SUSPENDED = (PARKED | SUSPENDED); // parking permit private volatile boolean parkPermit; @@ -434,17 +445,18 @@ final class VirtualThread extends BaseVirtualThread { * If yielding due to Thread.yield then it just submits the task to continue. */ private void afterYield() { - int s = state(); - assert (s == PARKING || s == YIELDING) && (carrierThread == null); + assert carrierThread == null; - if (s == PARKING) { - setState(PARKED); + int s = state(); + if (s == PARKING || s == TIMED_PARKING) { + int newState = (s == PARKING) ? PARKED : TIMED_PARKED; + setState(newState); // notify JVMTI that unmount has completed, thread is parked notifyJvmtiUnmount(/*hide*/false); // may have been unparked while parking - if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) { + if (parkPermit && compareAndSetState(newState, RUNNABLE)) { // lazy submit to continue on the current thread as carrier if possible if (currentThread() instanceof CarrierThread ct) { lazySubmitRunContinuation(ct.getPool()); @@ -465,6 +477,8 @@ final class VirtualThread extends BaseVirtualThread { } else { submitRunContinuation(); } + } else { + assert false; } } @@ -607,13 +621,13 @@ final class VirtualThread extends BaseVirtualThread { boolean yielded = false; Future unparker = scheduleUnpark(this::unpark, nanos); - setState(PARKING); + setState(TIMED_PARKING); try { yielded = yieldContinuation(); // may throw } finally { assert (Thread.currentThread() == this) && (yielded == (state() == RUNNING)); if (!yielded) { - assert state() == PARKING; + assert state() == TIMED_PARKING; setState(RUNNING); } cancel(unparker); @@ -645,7 +659,7 @@ final class VirtualThread extends BaseVirtualThread { event = null; } - setState(PINNED); + setState(timed ? TIMED_PINNED : PINNED); try { if (!parkPermit) { if (!timed) { @@ -713,7 +727,8 @@ final class VirtualThread extends BaseVirtualThread { Thread currentThread = Thread.currentThread(); if (!getAndSetParkPermit(true) && currentThread != this) { int s = state(); - if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) { + boolean parked = (s == PARKED) || (s == TIMED_PARKED); + if (parked && compareAndSetState(s, RUNNABLE)) { if (currentThread instanceof VirtualThread vthread) { vthread.switchToCarrierThread(); try { @@ -724,11 +739,11 @@ final class VirtualThread extends BaseVirtualThread { } else { submitRunContinuation(); } - } else if (s == PINNED) { + } else if ((s == PINNED) || (s == TIMED_PINNED)) { // unpark carrier thread when pinned. synchronized (carrierThreadAccessLock()) { Thread carrier = carrierThread; - if (carrier != null && state() == PINNED) { + if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) { U.unpark(carrier); } } @@ -865,7 +880,8 @@ final class VirtualThread extends BaseVirtualThread { @Override Thread.State threadState() { - switch (state()) { + int s = state(); + switch (s & ~SUSPENDED) { case NEW: return Thread.State.NEW; case STARTED: @@ -876,7 +892,6 @@ final class VirtualThread extends BaseVirtualThread { return Thread.State.RUNNABLE; } case RUNNABLE: - case RUNNABLE_SUSPENDED: // runnable, not mounted return Thread.State.RUNNABLE; case RUNNING: @@ -890,13 +905,16 @@ final class VirtualThread extends BaseVirtualThread { // runnable, mounted return Thread.State.RUNNABLE; case PARKING: + case TIMED_PARKING: case YIELDING: // runnable, mounted, not yet waiting return Thread.State.RUNNABLE; case PARKED: - case PARKED_SUSPENDED: case PINNED: - return Thread.State.WAITING; + return State.WAITING; + case TIMED_PARKED: + case TIMED_PINNED: + return State.TIMED_WAITING; case TERMINATED: return Thread.State.TERMINATED; default: @@ -936,7 +954,7 @@ final class VirtualThread extends BaseVirtualThread { private StackTraceElement[] tryGetStackTrace() { int initialState = state(); return switch (initialState) { - case RUNNABLE, PARKED -> { + case RUNNABLE, PARKED, TIMED_PARKED -> { int suspendedState = initialState | SUSPENDED; if (compareAndSetState(initialState, suspendedState)) { try { @@ -948,7 +966,7 @@ final class VirtualThread extends BaseVirtualThread { // re-submit if runnable // re-submit if unparked while suspended if (initialState == RUNNABLE - || (parkPermit && compareAndSetState(PARKED, RUNNABLE))) { + || (parkPermit && compareAndSetState(initialState, RUNNABLE))) { try { submitRunContinuation(); } catch (RejectedExecutionException ignore) { } diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java new file mode 100644 index 00000000000..17c17b9b1f5 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 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 id=default + * @bug 8312498 + * @summary Basic test for JVMTI GetThreadState with virtual threads + * @run junit/othervm/native GetThreadStateTest + */ + +/* + * @test id=no-vmcontinuations + * @requires vm.continuations + * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest + */ + +import java.util.StringJoiner; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class GetThreadStateTest { + + @BeforeAll + static void setup() { + System.loadLibrary("GetThreadStateTest"); + init(); + } + + /** + * Test state of new/unstarted thread. + */ + @Test + void testUnstarted() { + var thread = Thread.ofVirtual().unstarted(() -> { }); + check(thread, /*new*/ 0); + } + + /** + * Test state of terminated thread. + */ + @Test + void testTerminated() throws Exception { + var thread = Thread.ofVirtual().start(() -> { }); + thread.join(); + check(thread, JVMTI_THREAD_STATE_TERMINATED); + } + + /** + * Test state of runnable thread. + */ + @Test + void testRunnable() throws Exception { + var latch = new CountDownLatch(1); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + latch.countDown(); + + // spin until done + while (!done.get()) { + Thread.onSpinWait(); + } + }); + try { + // wait for thread to start execution + latch.await(); + + // thread should be runnable + int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE; + check(thread, expected); + + // re-test with interrupt status set + thread.interrupt(); + check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED); + } finally { + done.set(true); + thread.join(); + } + } + + /** + * Test state of thread waiting to enter a monitor. + */ + @Test + void testMonitorEnter() throws Exception { + var latch = new CountDownLatch(1); + Object lock = new Object(); + var thread = Thread.ofVirtual().unstarted(() -> { + latch.countDown(); + synchronized (lock) { } + }); + try { + synchronized (lock) { + // start thread and wait for it to start execution + thread.start(); + latch.await(); + + // thread should block on monitor enter + int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER; + await(thread, expected); + + // re-test with interrupt status set + thread.interrupt(); + check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED); + } + } finally { + thread.join(); + } + } + + /** + * Test state of thread waiting in Object.wait(). + */ + @Test + void testObjectWait() throws Exception { + var latch = new CountDownLatch(1); + Object lock = new Object(); + var thread = Thread.ofVirtual().start(() -> { + synchronized (lock) { + latch.countDown(); + try { + lock.wait(); + } catch (InterruptedException e) { } + } + }); + try { + // wait for thread to own monitor + latch.await(); + + // thread should wait + int expected = JVMTI_THREAD_STATE_ALIVE | + JVMTI_THREAD_STATE_WAITING | + JVMTI_THREAD_STATE_WAITING_INDEFINITELY | + JVMTI_THREAD_STATE_IN_OBJECT_WAIT; + await(thread, expected); + + // notify so thread waits to re-enter monitor + synchronized (lock) { + lock.notifyAll(); + expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER; + check(thread, expected); + + // re-test with interrupt status set + thread.interrupt(); + check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED); + } + } finally { + thread.interrupt(); + thread.join(); + } + } + + /** + * Test state of thread waiting in Object.wait(millis). + */ + @Test + void testObjectWaitMillis() throws Exception { + var latch = new CountDownLatch(1); + Object lock = new Object(); + var thread = Thread.ofVirtual().start(() -> { + synchronized (lock) { + latch.countDown(); + try { + lock.wait(Long.MAX_VALUE); + } catch (InterruptedException e) { } + } + }); + try { + // wait for thread to own monitor + latch.await(); + + // thread should wait + int expected = JVMTI_THREAD_STATE_ALIVE | + JVMTI_THREAD_STATE_WAITING | + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT | + JVMTI_THREAD_STATE_IN_OBJECT_WAIT; + await(thread, expected); + + // notify so thread waits to re-enter monitor + synchronized (lock) { + lock.notifyAll(); + expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER; + check(thread, expected); + + // re-test with interrupt status set + thread.interrupt(); + check(thread, expected | JVMTI_THREAD_STATE_INTERRUPTED); + } + } finally { + thread.interrupt(); + thread.join(); + } + } + + /** + * Test state of thread parked with LockSupport.park. + */ + @Test + void testPark() throws Exception { + var latch = new CountDownLatch(1); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + latch.countDown(); + while (!done.get()) { + LockSupport.park(); + } + }); + try { + // wait for thread to start execution + latch.await(); + + // thread should park + int expected = JVMTI_THREAD_STATE_ALIVE | + JVMTI_THREAD_STATE_WAITING | + JVMTI_THREAD_STATE_WAITING_INDEFINITELY | + JVMTI_THREAD_STATE_PARKED; + await(thread, expected); + } finally { + done.set(true); + LockSupport.unpark(thread); + thread.join(); + } + } + + /** + * Test state of thread parked with LockSupport.parkNanos. + */ + @Test + void testParkNanos() throws Exception { + var latch = new CountDownLatch(1); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + latch.countDown(); + while (!done.get()) { + LockSupport.parkNanos(Long.MAX_VALUE); + } + }); + try { + // wait for thread to start execution + latch.await(); + + // 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 latch = new CountDownLatch(1); + Object lock = new Object(); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + synchronized (lock) { + latch.countDown(); + while (!done.get()) { + LockSupport.park(); + } + } + }); + try { + // wait for thread to own monitor + latch.await(); + + // 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 latch = new CountDownLatch(1); + Object lock = new Object(); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + synchronized (lock) { + latch.countDown(); + while (!done.get()) { + LockSupport.parkNanos(Long.MAX_VALUE); + } + } + }); + try { + // wait for thread to own monitor + latch.await(); + + // 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(); + } + } + + /** + * Asserts that the given thread has the expected JVMTI state. + */ + private static void check(Thread thread, int expected) { + System.err.format(" expect state=0x%x (%s) ...%n", expected, jvmtiStateToString(expected)); + int state = jvmtiState(thread); + System.err.format(" thread state=0x%x (%s)%n", state, jvmtiStateToString(state)); + assertEquals(expected, state); + } + + /** + * Waits indefinitely for the given thread to get to the target JVMTI state. + */ + private static void await(Thread thread, int targetState) throws Exception { + System.err.format(" await state=0x%x (%s) ...%n", targetState, jvmtiStateToString(targetState)); + int state = jvmtiState(thread); + System.err.format(" thread state=0x%x (%s)%n", state, jvmtiStateToString(state)); + while (state != targetState) { + assertTrue(thread.isAlive(), "Thread has terminated"); + Thread.sleep(20); + state = jvmtiState(thread); + System.err.format(" thread state=0x%x (%s)%n", state, jvmtiStateToString(state)); + } + } + + private static final int JVMTI_THREAD_STATE_ALIVE = 0x0001; + private static final int JVMTI_THREAD_STATE_TERMINATED = 0x0002; + private static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004; + private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400; + private static final int JVMTI_THREAD_STATE_WAITING = 0x0080; + private static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010; + private static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020; + private static final int JVMTI_THREAD_STATE_SLEEPING = 0x0040; + private static final int JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100; + private static final int JVMTI_THREAD_STATE_PARKED = 0x0200; + private static final int JVMTI_THREAD_STATE_SUSPENDED = 0x100000; + private static final int JVMTI_THREAD_STATE_INTERRUPTED = 0x200000; + private static final int JVMTI_THREAD_STATE_IN_NATIVE = 0x400000; + + private static native void init(); + private static native int jvmtiState(Thread thread); + + private static String jvmtiStateToString(int state) { + StringJoiner sj = new StringJoiner(" | "); + if ((state & JVMTI_THREAD_STATE_ALIVE) != 0) + sj.add("JVMTI_THREAD_STATE_ALIVE"); + if ((state & JVMTI_THREAD_STATE_TERMINATED) != 0) + sj.add("JVMTI_THREAD_STATE_TERMINATED"); + if ((state & JVMTI_THREAD_STATE_RUNNABLE) != 0) + sj.add("JVMTI_THREAD_STATE_RUNNABLE"); + if ((state & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0) + sj.add("JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER"); + if ((state & JVMTI_THREAD_STATE_WAITING) != 0) + sj.add("JVMTI_THREAD_STATE_WAITING"); + if ((state & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0) + sj.add("JVMTI_THREAD_STATE_WAITING_INDEFINITELY"); + if ((state & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0) + sj.add("JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT"); + if ((state & JVMTI_THREAD_STATE_IN_OBJECT_WAIT) != 0) + sj.add("JVMTI_THREAD_STATE_IN_OBJECT_WAIT"); + if ((state & JVMTI_THREAD_STATE_PARKED) != 0) + sj.add("JVMTI_THREAD_STATE_PARKED"); + if ((state & JVMTI_THREAD_STATE_SUSPENDED) != 0) + sj.add("JVMTI_THREAD_STATE_SUSPENDED"); + if ((state & JVMTI_THREAD_STATE_INTERRUPTED) != 0) + sj.add("JVMTI_THREAD_STATE_INTERRUPTED"); + if ((state & JVMTI_THREAD_STATE_IN_NATIVE) != 0) + sj.add("JVMTI_THREAD_STATE_IN_NATIVE"); + String s = sj.toString(); + return s.isEmpty() ? "" : s; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/libGetThreadStateTest.c b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/libGetThreadStateTest.c new file mode 100644 index 00000000000..532225670f5 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/libGetThreadStateTest.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 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. + */ + +#include "jni.h" +#include "jvmti.h" + +static jvmtiEnv *jvmti; + +JNIEXPORT void JNICALL Java_GetThreadStateTest_init(JNIEnv *env, jclass clazz) { + JavaVM* vm; + jint res; + res = (*env)->GetJavaVM(env, &vm); + if (res != 0) { + (*env)->FatalError(env, "GetJavaVM failed"); + } else { + res = (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION); + if (res != JNI_OK) { + (*env)->FatalError(env, "GetEnv failed"); + } + } +} + +JNIEXPORT jint JNICALL Java_GetThreadStateTest_jvmtiState(JNIEnv *env, jclass clazz, jobject thread) { + jvmtiError err; + jint state = 0; + err = (*jvmti)->GetThreadState(jvmti, thread, &state); + if (err != JVMTI_ERROR_NONE) { + (*env)->FatalError(env, "GetThreadState failed"); + } + return state; +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java index 554e83c231c..61b94a4484b 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java @@ -149,12 +149,12 @@ public class VThreadEventTest { } await(ready0); mready.countDown(); - await(ready1); // to guaranty state is not State.WAITING after await(mready) in test1() - // wait for test1 threads to reach WAITING state in sleep() + 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.WAITING) { + while (state != Thread.State.TIMED_WAITING) { Thread.sleep(10); state = t.getState(); log("DBG: state: " + state); diff --git a/test/jdk/java/lang/Thread/virtual/CustomScheduler.java b/test/jdk/java/lang/Thread/virtual/CustomScheduler.java index 6d4e029f280..5bde81905ec 100644 --- a/test/jdk/java/lang/Thread/virtual/CustomScheduler.java +++ b/test/jdk/java/lang/Thread/virtual/CustomScheduler.java @@ -45,7 +45,6 @@ import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.*; class CustomScheduler { - private static final Executor DEFAULT_SCHEDULER = defaultScheduler(); private static ExecutorService scheduler1; private static ExecutorService scheduler2; @@ -216,20 +215,6 @@ class CustomScheduler { } } - /** - * Returns the default scheduler. - */ - private static Executor defaultScheduler() { - try { - Field defaultScheduler = Class.forName("java.lang.VirtualThread") - .getDeclaredField("DEFAULT_SCHEDULER"); - defaultScheduler.setAccessible(true); - return (Executor) defaultScheduler.get(null); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - /** * Returns the scheduler for the given virtual thread. */ diff --git a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java index 8afb472d3f4..27d21a79346 100644 --- a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java +++ b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java @@ -106,7 +106,7 @@ class ThreadAPI { LockSupport.park(); after.set(Thread.currentThread()); }); - awaitParked(thread); + await(thread, Thread.State.WAITING); LockSupport.unpark(thread); thread.join(); assertTrue(before.get() == thread); @@ -130,7 +130,7 @@ class ThreadAPI { }); synchronized (lock) { thread.start(); - awaitBlocked(thread); + await(thread, Thread.State.BLOCKED); } thread.join(); assertTrue(ref1.get() == thread); @@ -160,7 +160,7 @@ class ThreadAPI { lock.lock(); try { thread.start(); - awaitParked(thread); + await(thread, Thread.State.WAITING); } finally { lock.unlock(); } @@ -765,6 +765,7 @@ class ThreadAPI { assertFalse(thread.join(Duration.ofMillis(100))); } finally { done.set(true); + thread.join(); } } @@ -897,7 +898,7 @@ class ThreadAPI { exception.set(e); } }); - awaitParked(thread); + await(thread, Thread.State.TIMED_WAITING); thread.interrupt(); thread.join(); assertNull(exception.get()); @@ -917,7 +918,7 @@ class ThreadAPI { exception.set(e); } }); - awaitParked(thread); + await(thread, Thread.State.WAITING); thread.interrupt(); thread.join(); assertNull(exception.get()); @@ -1032,16 +1033,16 @@ class ThreadAPI { void testSetPriority1() throws Exception { VThreadRunner.run(() -> { Thread me = Thread.currentThread(); - assertTrue(me.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, me.getPriority()); me.setPriority(Thread.MAX_PRIORITY); - assertTrue(me.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, me.getPriority()); me.setPriority(Thread.NORM_PRIORITY); - assertTrue(me.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, me.getPriority()); me.setPriority(Thread.MIN_PRIORITY); - assertTrue(me.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, me.getPriority()); assertThrows(IllegalArgumentException.class, () -> me.setPriority(-1)); }); @@ -1055,33 +1056,33 @@ class ThreadAPI { var thread = Thread.ofVirtual().unstarted(LockSupport::park); // not started - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); thread.setPriority(Thread.MAX_PRIORITY); - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); thread.setPriority(Thread.NORM_PRIORITY); - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); thread.setPriority(Thread.MIN_PRIORITY); - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1)); // running thread.start(); try { - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); thread.setPriority(Thread.NORM_PRIORITY); thread.setPriority(Thread.MAX_PRIORITY); - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); thread.setPriority(Thread.NORM_PRIORITY); - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); thread.setPriority(Thread.MIN_PRIORITY); - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); assertThrows(IllegalArgumentException.class, () -> thread.setPriority(-1)); @@ -1091,7 +1092,7 @@ class ThreadAPI { thread.join(); // terminated - assertTrue(thread.getPriority() == Thread.NORM_PRIORITY); + assertEquals(Thread.NORM_PRIORITY, thread.getPriority()); } /** @@ -1650,53 +1651,79 @@ class ThreadAPI { } /** - * Test Thread::getState when thread is not started. + * Test Thread::getState when thread is new/unstarted. */ @Test void testGetState1() { var thread = Thread.ofVirtual().unstarted(() -> { }); - assertTrue(thread.getState() == Thread.State.NEW); + assertEquals(Thread.State.NEW, thread.getState()); + } + + /** + * Test Thread::getState when thread is terminated. + */ + @Test + void testGetState2() throws Exception { + var thread = Thread.ofVirtual().start(() -> { }); + thread.join(); + assertEquals(Thread.State.TERMINATED, thread.getState()); } /** * Test Thread::getState when thread is runnable (mounted). */ @Test - void testGetState2() throws Exception { - VThreadRunner.run(() -> { - Thread.State state = Thread.currentThread().getState(); - assertTrue(state == Thread.State.RUNNABLE); + void testGetState3() throws Exception { + var started = new CountDownLatch(1); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + started.countDown(); + + // spin until done + while (!done.get()) { + Thread.onSpinWait(); + } }); + try { + // wait for thread to start + started.await(); + + // thread should be runnable + assertEquals(Thread.State.RUNNABLE, thread.getState()); + } finally { + done.set(true); + thread.join(); + } } /** * Test Thread::getState when thread is runnable (not mounted). */ @Test - void testGetState3() throws Exception { + void testGetState4() throws Exception { assumeTrue(ThreadBuilders.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); - assertTrue(t2.getState() == Thread.State.NEW); + assertEquals(Thread.State.NEW, t2.getState()); // start t2 to make it runnable t2.start(); try { - assertTrue(t2.getState() == Thread.State.RUNNABLE); + assertEquals(Thread.State.RUNNABLE, t2.getState()); // yield to allow t2 to run and park Thread.yield(); - assertTrue(t2.getState() == Thread.State.WAITING); + assertEquals(Thread.State.WAITING, t2.getState()); } finally { // unpark t2 to make it runnable again LockSupport.unpark(t2); } // t2 should be runnable (not mounted) - assertTrue(t2.getState() == Thread.State.RUNNABLE); + assertEquals(Thread.State.RUNNABLE, t2.getState()); completed.set(true); }); @@ -1706,48 +1733,21 @@ class ThreadAPI { } /** - * Test Thread::getState when thread is parked. - */ - @Test - void testGetState4() throws Exception { - var thread = Thread.ofVirtual().start(LockSupport::park); - while (thread.getState() != Thread.State.WAITING) { - Thread.sleep(20); - } - LockSupport.unpark(thread); - thread.join(); - } - - /** - * Test Thread::getState when thread is parked while holding a monitor. + * Test Thread::getState when thread is waiting to enter a monitor. */ @Test void testGetState5() throws Exception { - var thread = Thread.ofVirtual().start(() -> { - synchronized (lock) { - LockSupport.park(); - } - }); - while (thread.getState() != Thread.State.WAITING) { - Thread.sleep(20); - } - LockSupport.unpark(thread); - thread.join(); - } - - /** - * Test Thread::getState when thread is waiting for a monitor. - */ - @Test - void testGetState6() throws Exception { + var started = new CountDownLatch(1); var thread = Thread.ofVirtual().unstarted(() -> { + started.countDown(); synchronized (lock) { } }); synchronized (lock) { thread.start(); - while (thread.getState() != Thread.State.BLOCKED) { - Thread.sleep(20); - } + started.await(); + + // wait for thread to block + await(thread, Thread.State.BLOCKED); } thread.join(); } @@ -1756,27 +1756,124 @@ class ThreadAPI { * Test Thread::getState when thread is waiting in Object.wait. */ @Test - void testGetState7() throws Exception { + void testGetState6() throws Exception { var thread = Thread.ofVirtual().start(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { } } }); - while (thread.getState() != Thread.State.WAITING) { - Thread.sleep(20); + try { + // wait for thread to wait + await(thread, Thread.State.WAITING); + } finally { + thread.interrupt(); + thread.join(); } - thread.interrupt(); - thread.join(); } /** - * Test Thread::getState when thread is terminated. + * Test Thread::getState when thread is waiting in Object.wait(millis). + */ + @Test + void testGetState7() throws Exception { + var thread = Thread.ofVirtual().start(() -> { + synchronized (lock) { + try { + lock.wait(Long.MAX_VALUE); + } catch (InterruptedException e) { } + } + }); + try { + // wait for thread to wait + await(thread, Thread.State.TIMED_WAITING); + } finally { + thread.interrupt(); + thread.join(); + } + } + + /** + * Test Thread::getState when thread is parked. */ @Test void testGetState8() throws Exception { - var thread = Thread.ofVirtual().start(() -> { }); - thread.join(); - assertTrue(thread.getState() == Thread.State.TERMINATED); + var thread = Thread.ofVirtual().start(LockSupport::park); + try { + await(thread, Thread.State.WAITING); + } finally { + LockSupport.unpark(thread); + thread.join(); + } + } + + /** + * Test Thread::getState when thread is timed parked. + */ + @Test + void testGetState9() throws Exception { + var thread = Thread.ofVirtual().start(() -> LockSupport.parkNanos(Long.MAX_VALUE)); + try { + await(thread, Thread.State.TIMED_WAITING); + } finally { + LockSupport.unpark(thread); + thread.join(); + } + } + + /** + * Test Thread::getState when thread is parked while holding a monitor. + */ + @Test + void testGetState10() throws Exception { + var started = new CountDownLatch(1); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + started.countDown(); + synchronized (lock) { + while (!done.get()) { + LockSupport.park(); + } + } + }); + try { + // wait for thread to start + started.await(); + + // wait for thread to park + await(thread, Thread.State.WAITING); + } finally { + done.set(true); + LockSupport.unpark(thread); + thread.join(); + } + } + + /** + * Test Thread::getState when thread is timed parked while holding a monitor. + */ + @Test + void testGetState11() throws Exception { + var started = new CountDownLatch(1); + var done = new AtomicBoolean(); + var thread = Thread.ofVirtual().start(() -> { + started.countDown(); + synchronized (lock) { + while (!done.get()) { + LockSupport.parkNanos(Long.MAX_VALUE); + } + } + }); + try { + // wait for thread to start + started.await(); + + // wait for thread to park + await(thread, Thread.State.TIMED_WAITING); + } finally { + done.set(true); + LockSupport.unpark(thread); + thread.join(); + } } /** @@ -1899,9 +1996,7 @@ class ThreadAPI { } // wait for virtual thread to block in wait - while (vthread.getState() != Thread.State.WAITING) { - Thread.sleep(20); - } + await(vthread, Thread.State.WAITING); // get stack trace of both carrier and virtual thread StackTraceElement[] carrierStackTrace = carrier.getStackTrace(); @@ -1928,12 +2023,7 @@ class ThreadAPI { @Test void testGetStackTrace5() throws Exception { var thread = Thread.ofVirtual().start(LockSupport::park); - - // wait for thread to park - while (thread.getState() != Thread.State.WAITING) { - Thread.sleep(20); - } - + await(thread, Thread.State.WAITING); try { StackTraceElement[] stack = thread.getStackTrace(); assertTrue(contains(stack, "LockSupport.park")); @@ -1996,9 +2086,7 @@ class ThreadAPI { } // wait for virtual thread to block in wait - while (vthread.getState() != Thread.State.WAITING) { - Thread.sleep(20); - } + await(vthread, Thread.State.WAITING); // get all stack traces Map map = Thread.getAllStackTraces(); @@ -2034,7 +2122,7 @@ class ThreadAPI { var vgroup = thread.getThreadGroup(); thread.start(); try { - assertTrue(thread.getThreadGroup() == vgroup); + assertEquals(vgroup, thread.getThreadGroup()); } finally { LockSupport.unpark(thread); thread.join(); @@ -2051,7 +2139,7 @@ class ThreadAPI { ThreadGroup vgroup = Thread.currentThread().getThreadGroup(); Thread child = new Thread(() -> { }); ThreadGroup group = child.getThreadGroup(); - assertTrue(group == vgroup); + assertEquals(vgroup, group); }); } @@ -2068,19 +2156,19 @@ class ThreadAPI { thread.join(); ThreadGroup vgroup = ref.get(); - assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY); + assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority()); ThreadGroup group = new ThreadGroup(vgroup, "group"); assertTrue(group.getParent() == vgroup); - assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY); + assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority()); vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1); - assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY); - assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1); + assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority()); + assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority()); vgroup.setMaxPriority(Thread.MIN_PRIORITY); - assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY); - assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY); + assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority()); + assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority()); } /** @@ -2091,20 +2179,19 @@ class ThreadAPI { void testThreadGroup4() throws Exception { VThreadRunner.run(() -> { ThreadGroup vgroup = Thread.currentThread().getThreadGroup(); - - assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY); + assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority()); ThreadGroup group = new ThreadGroup("group"); - assertTrue(group.getParent() == vgroup); - assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY); + assertEquals(vgroup, group.getParent()); + assertEquals(Thread.MAX_PRIORITY, group.getMaxPriority()); vgroup.setMaxPriority(Thread.MAX_PRIORITY - 1); - assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY); - assertTrue(group.getMaxPriority() == Thread.MAX_PRIORITY - 1); + assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority()); + assertEquals(Thread.MAX_PRIORITY - 1, group.getMaxPriority()); vgroup.setMaxPriority(Thread.MIN_PRIORITY); - assertTrue(vgroup.getMaxPriority() == Thread.MAX_PRIORITY); - assertTrue(group.getMaxPriority() == Thread.MIN_PRIORITY); + assertEquals(Thread.MAX_PRIORITY, vgroup.getMaxPriority()); + assertEquals(Thread.MIN_PRIORITY, group.getMaxPriority()); }); } @@ -2208,9 +2295,7 @@ class ThreadAPI { me.setName("fred"); LockSupport.park(); }); - while (thread.getState() != Thread.State.WAITING) { - Thread.sleep(10); - } + await(thread, Thread.State.WAITING); try { assertTrue(thread.toString().contains("fred")); } finally { @@ -2233,23 +2318,11 @@ class ThreadAPI { } /** - * Waits for the given thread to park. + * Waits for the given thread to reach a given state. */ - static void awaitParked(Thread thread) throws InterruptedException { + private void await(Thread thread, Thread.State expectedState) throws InterruptedException { Thread.State state = thread.getState(); - while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) { - assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); - Thread.sleep(10); - state = thread.getState(); - } - } - - /** - * Waits for the given thread to block waiting on a monitor. - */ - static void awaitBlocked(Thread thread) throws InterruptedException { - Thread.State state = thread.getState(); - while (state != Thread.State.BLOCKED) { + while (state != expectedState) { assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); Thread.sleep(10); state = thread.getState(); diff --git a/test/jdk/java/lang/Thread/virtual/ThreadBuilders.java b/test/jdk/java/lang/Thread/virtual/ThreadBuilders.java index 9283bf99321..a6a5d6abdbc 100644 --- a/test/jdk/java/lang/Thread/virtual/ThreadBuilders.java +++ b/test/jdk/java/lang/Thread/virtual/ThreadBuilders.java @@ -51,7 +51,6 @@ class ThreadBuilders { * @throws UnsupportedOperationException if custom schedulers are not supported */ static Thread.Builder.OfVirtual virtualThreadBuilder(Executor scheduler) { - Thread.Builder.OfVirtual builder = Thread.ofVirtual(); try { return (Thread.Builder.OfVirtual) VTBUILDER_CTOR.newInstance(scheduler); } catch (InvocationTargetException e) { diff --git a/test/jdk/java/lang/Thread/virtual/stress/ParkALot.java b/test/jdk/java/lang/Thread/virtual/stress/ParkALot.java new file mode 100644 index 00000000000..a5eb98d207d --- /dev/null +++ b/test/jdk/java/lang/Thread/virtual/stress/ParkALot.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 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 Stress test parking and unparking + * @requires vm.debug != true + * @run main/othervm ParkALot 500000 + */ + +/* + * @test + * @requires vm.debug == true + * @run main/othervm ParkALot 200000 + */ + +import java.time.Instant; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.locks.LockSupport; + +public class ParkALot { + private static final int ITERATIONS = 1_000_000; + + public static void main(String[] args) { + int iterations; + if (args.length > 0) { + iterations = Integer.parseInt(args[0]); + } else { + iterations = ITERATIONS; + } + + int maxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 2, 1); + for (int nthreads = 1; nthreads <= maxThreads; nthreads++) { + System.out.format("%s %d threads ...%n", Instant.now(), nthreads); + ThreadFactory factory = Thread.ofPlatform().factory(); + try (var executor = Executors.newThreadPerTaskExecutor(factory)) { + for (int i = 0; i < nthreads; i++) { + executor.submit(() -> parkALot(iterations)); + } + } + } + } + + /** + * Creates a virtual thread that alternates between untimed and timed parking. + * A platform thread spins unparking the virtual thread. + */ + private static void parkALot(int iterations) { + Thread vthread = Thread.ofVirtual().start(() -> { + int i = 0; + boolean timed = false; + while (i < iterations) { + if (timed) { + LockSupport.parkNanos(Long.MAX_VALUE); + timed = false; + } else { + LockSupport.park(); + timed = true; + } + i++; + } + }); + + Thread.State state; + while ((state = vthread.getState()) != Thread.State.TERMINATED) { + if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) { + LockSupport.unpark(vthread); + } else { + Thread.onSpinWait(); + } + } + } +}