8320707: Virtual thread test updates

Reviewed-by: jpai
This commit is contained in:
Alan Bateman 2024-01-03 14:59:03 +00:00
parent 7eb25ec7b3
commit b67b71cd87
15 changed files with 625 additions and 220 deletions

View File

@ -25,20 +25,22 @@
* @test id=default * @test id=default
* @bug 8312498 * @bug 8312498
* @summary Basic test for JVMTI GetThreadState with virtual threads * @summary Basic test for JVMTI GetThreadState with virtual threads
* @library /test/lib
* @run junit/othervm/native GetThreadStateTest * @run junit/othervm/native GetThreadStateTest
*/ */
/* /*
* @test id=no-vmcontinuations * @test id=no-vmcontinuations
* @requires vm.continuations * @requires vm.continuations
* @library /test/lib
* @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations GetThreadStateTest
*/ */
import java.util.StringJoiner; import java.util.StringJoiner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -75,10 +77,10 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testRunnable() throws Exception { void testRunnable() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
latch.countDown(); started.set(true);
// spin until done // spin until done
while (!done.get()) { while (!done.get()) {
@ -87,7 +89,7 @@ class GetThreadStateTest {
}); });
try { try {
// wait for thread to start execution // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should be runnable // thread should be runnable
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE; int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE;
@ -107,17 +109,17 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testMonitorEnter() throws Exception { void testMonitorEnter() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
Object lock = new Object(); Object lock = new Object();
var thread = Thread.ofVirtual().unstarted(() -> { var thread = Thread.ofVirtual().unstarted(() -> {
latch.countDown(); started.set(true);
synchronized (lock) { } synchronized (lock) { }
}); });
try { try {
synchronized (lock) { synchronized (lock) {
// start thread and wait for it to start execution // start thread and wait for it to start execution
thread.start(); thread.start();
latch.await(); awaitTrue(started);
// thread should block on monitor enter // thread should block on monitor enter
int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER; int expected = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
@ -137,19 +139,19 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testObjectWait() throws Exception { void testObjectWait() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
Object lock = new Object(); Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
latch.countDown(); started.set(true);
try { try {
lock.wait(); lock.wait();
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
}); });
try { try {
// wait for thread to own monitor // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should wait // thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -179,19 +181,19 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testObjectWaitMillis() throws Exception { void testObjectWaitMillis() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
Object lock = new Object(); Object lock = new Object();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { synchronized (lock) {
latch.countDown(); started.set(true);
try { try {
lock.wait(Long.MAX_VALUE); lock.wait(Long.MAX_VALUE);
} catch (InterruptedException e) { } } catch (InterruptedException e) { }
} }
}); });
try { try {
// wait for thread to own monitor // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should wait // thread should wait
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -221,17 +223,17 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testPark() throws Exception { void testPark() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
latch.countDown(); started.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.park(); LockSupport.park();
} }
}); });
try { try {
// wait for thread to start execution // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should park // thread should park
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -251,17 +253,17 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testParkNanos() throws Exception { void testParkNanos() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
latch.countDown(); started.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE); LockSupport.parkNanos(Long.MAX_VALUE);
} }
}); });
try { try {
// wait for thread to start execution // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should park // thread should park
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -281,20 +283,19 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testParkWhenPinned() throws Exception { void testParkWhenPinned() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
Object lock = new Object();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { VThreadPinner.runPinned(() -> {
latch.countDown(); started.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.park(); LockSupport.park();
} }
} });
}); });
try { try {
// wait for thread to own monitor // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should park // thread should park
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -314,20 +315,19 @@ class GetThreadStateTest {
*/ */
@Test @Test
void testParkNanosWhenPinned() throws Exception { void testParkNanosWhenPinned() throws Exception {
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
Object lock = new Object();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> { var thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { VThreadPinner.runPinned(() -> {
latch.countDown(); started.set(true);
while (!done.get()) { while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE); LockSupport.parkNanos(Long.MAX_VALUE);
} }
} });
}); });
try { try {
// wait for thread to own monitor // wait for thread to start execution
latch.await(); awaitTrue(started);
// thread should park // thread should park
int expected = JVMTI_THREAD_STATE_ALIVE | int expected = JVMTI_THREAD_STATE_ALIVE |
@ -342,6 +342,15 @@ class GetThreadStateTest {
} }
} }
/**
* Waits for the boolean value to become true.
*/
private static void awaitTrue(AtomicBoolean ref) throws Exception {
while (!ref.get()) {
Thread.sleep(20);
}
}
/** /**
* Asserts that the given thread has the expected JVMTI state. * Asserts that the given thread has the expected JVMTI state.
*/ */

View File

@ -66,7 +66,7 @@ public class StressStackOverflow {
TestFailureException(String s) { super(s); } TestFailureException(String s) { super(s); }
} }
static final long DURATION_IN_NANOS = Duration.ofMinutes(2).toNanos(); static final long DURATION_IN_NANOS = Duration.ofMinutes(1).toNanos();
// Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable // Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable
// and Runnable interfaces. Which one gets tested depends on the constructor argument. // and Runnable interfaces. Which one gets tested depends on the constructor argument.

View File

@ -40,7 +40,6 @@
import java.lang.management.LockInfo; import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo; import java.lang.management.ThreadInfo;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -55,38 +54,53 @@ class CarrierThreadWaits {
void testCarrierThreadWaiting() throws Exception { void testCarrierThreadWaiting() throws Exception {
try (ForkJoinPool pool = new ForkJoinPool(1)) { try (ForkJoinPool pool = new ForkJoinPool(1)) {
var carrierRef = new AtomicReference<Thread>(); var carrierRef = new AtomicReference<Thread>();
var vthreadRef = new AtomicReference<Thread>();
Executor scheduler = task -> { Executor scheduler = task -> {
pool.submit(() -> { pool.submit(() -> {
carrierRef.set(Thread.currentThread()); Thread carrier = Thread.currentThread();
carrierRef.set(carrier);
Thread vthread = vthreadRef.get();
System.err.format("%s run task (%s) ...%n", carrier, vthread);
task.run(); task.run();
System.err.format("%s task done (%s)%n", carrier, vthread);
}); });
}; };
// start a virtual thread that spins and remains mounted until "done" // start a virtual thread that spins and remains mounted until "done"
var latch = new CountDownLatch(1); var started = new AtomicBoolean();
var done = new AtomicBoolean(); var done = new AtomicBoolean();
Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
Thread vthread = builder.start(() -> { Thread vthread = builder.unstarted(() -> {
latch.countDown(); started.set(true);
while (!done.get()) { while (!done.get()) {
Thread.onSpinWait(); Thread.onSpinWait();
} }
}); });
vthreadRef.set(vthread);
// wait for virtual thread to execute vthread.start();
latch.await();
try { try {
long carrierId = carrierRef.get().threadId(); // wait for virtual thread to start
while (!started.get()) {
Thread.sleep(10);
}
Thread carrier = carrierRef.get();
long carrierId = carrier.threadId();
long vthreadId = vthread.threadId(); long vthreadId = vthread.threadId();
// carrier thread should be on WAITING on virtual thread // carrier thread should be on WAITING on virtual thread
ThreadInfo ti = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId); ThreadInfo ti = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId);
assertTrue(ti.getThreadState() == Thread.State.WAITING); Thread.State state = ti.getThreadState();
assertEquals(vthread.getClass().getName(), ti.getLockInfo().getClassName()); LockInfo lockInfo = ti.getLockInfo();
assertTrue(ti.getLockInfo().getIdentityHashCode() == System.identityHashCode(vthread)); assertEquals(Thread.State.WAITING, state);
assertTrue(ti.getLockOwnerId() == vthreadId); assertNotNull(lockInfo);
assertEquals(vthread.getClass().getName(), lockInfo.getClassName());
assertEquals(System.identityHashCode(vthread), lockInfo.getIdentityHashCode());
assertEquals(vthreadId, ti.getLockOwnerId());
} finally { } finally {
done.set(true); done.set(true);
} }

View File

@ -29,35 +29,34 @@
*/ */
import java.io.IOException; import java.io.IOException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.Selector;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
public class GetStackTraceWhenRunnable { public class GetStackTraceWhenRunnable {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
try (Selector sel = Selector.open()) {
// start thread1 and wait for it to park // start thread1 and wait for it to park
Thread thread1 = Thread.startVirtualThread(LockSupport::park); Thread thread1 = Thread.startVirtualThread(LockSupport::park);
while (thread1.getState() != Thread.State.WAITING) { while (thread1.getState() != Thread.State.WAITING) {
Thread.sleep(20); 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);
} }
// start thread2 to pin the carrier thread
CountDownLatch latch = new CountDownLatch(1);
Thread thread2 = Thread.startVirtualThread(() -> {
latch.countDown();
try {
sel.select();
} catch (ClosedSelectorException e) {
// expected
} catch (IOException ioe) {
ioe.printStackTrace();
}
});
latch.await(); // wait for thread2 to run
// unpark thread1 and check that it is "stuck" in the runnable state // unpark thread1 and check that it is "stuck" in the runnable state
// (the carrier thread is pinned, no other virtual thread can run) // (the carrier thread is pinned, no other virtual thread can run)
@ -73,6 +72,10 @@ public class GetStackTraceWhenRunnable {
for (StackTraceElement e : stack) { for (StackTraceElement e : stack) {
System.out.println(e); System.out.println(e);
} }
} finally {
done.set(true);
thread2.join();
thread1.join();
} }
} }

View File

@ -26,12 +26,12 @@
* @summary Basic test for JFR jdk.VirtualThreadXXX events * @summary Basic test for JFR jdk.VirtualThreadXXX events
* @requires vm.continuations * @requires vm.continuations
* @modules jdk.jfr java.base/java.lang:+open * @modules jdk.jfr java.base/java.lang:+open
* @run junit/othervm JfrEvents * @library /test/lib
* @run junit/othervm --enable-native-access=ALL-UNNAMED JfrEvents
*/ */
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -39,20 +39,27 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jfr.EventType; import jdk.jfr.EventType;
import jdk.jfr.Recording; import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile; import jdk.jfr.consumer.RecordingFile;
import jdk.test.lib.thread.VThreadPinner;
import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class JfrEvents { class JfrEvents {
private static final Object lock = new Object();
/** /**
* Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events. * Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
@ -85,45 +92,90 @@ 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.
*/ */
@Test @ParameterizedTest
void testVirtualThreadPinned() throws Exception { @MethodSource("pinnedCases")
Runnable[] parkers = new Runnable[] { void testVirtualThreadPinned(String label,
() -> LockSupport.park(), ThrowingRunnable<Exception> parker,
() -> LockSupport.parkNanos(Duration.ofDays(1).toNanos()) Thread.State expectedState,
}; Consumer<Thread> unparker) throws Exception {
try (Recording recording = new Recording()) { try (Recording recording = new Recording()) {
recording.enable("jdk.VirtualThreadPinned"); recording.enable("jdk.VirtualThreadPinned");
recording.start(); recording.start();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { try {
for (Runnable parker : parkers) { var exception = new AtomicReference<Throwable>();
// execute parking task in virtual thread var thread = Thread.ofVirtual().start(() -> {
var threadRef = new AtomicReference<Thread>();
executor.submit(() -> {
threadRef.set(Thread.currentThread());
synchronized (lock) {
parker.run(); // should pin carrier
}
});
// wait for the task to start and the virtual thread to park
Thread thread;
while ((thread = threadRef.get()) == null) {
Thread.sleep(10);
}
try { try {
Thread.State state = thread.getState(); parker.run();
while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) { } catch (Throwable e) {
Thread.sleep(10); exception.set(e);
state = thread.getState();
}
} finally {
LockSupport.unpark(thread);
} }
});
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());
} }
} finally { } finally {
recording.stop(); recording.stop();
@ -132,9 +184,9 @@ class JfrEvents {
Map<String, Integer> events = sumEvents(recording); Map<String, Integer> events = sumEvents(recording);
System.err.println(events); System.err.println(events);
// should have a pinned event for each park // should have at least one pinned event
int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0); int pinnedCount = events.getOrDefault("jdk.VirtualThreadPinned", 0);
assertEquals(parkers.length, pinnedCount); assertTrue(pinnedCount >= 1, "Expected one or more events");
} }
} }

View File

@ -24,8 +24,9 @@
/** /**
* @test * @test
* @summary Test virtual threads using Object.wait/notifyAll * @summary Test virtual threads using Object.wait/notifyAll
* @modules java.base/java.lang:+open
* @library /test/lib * @library /test/lib
* @run junit WaitNotify * @run junit MonitorWaitNotify
*/ */
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@ -34,7 +35,7 @@ import jdk.test.lib.thread.VThreadRunner;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class WaitNotify { class MonitorWaitNotify {
/** /**
* Test virtual thread waits, notified by platform thread. * Test virtual thread waits, notified by platform thread.
@ -84,24 +85,31 @@ class WaitNotify {
*/ */
@Test @Test
void testWaitNotify3() throws Exception { void testWaitNotify3() throws Exception {
var lock = new Object(); // need at least two carrier threads due to pinning
var ready = new Semaphore(0); int previousParallelism = VThreadRunner.ensureParallelism(2);
var thread1 = Thread.ofVirtual().start(() -> { try {
synchronized (lock) { var lock = new Object();
ready.release(); var ready = new Semaphore(0);
try { var thread1 = Thread.ofVirtual().start(() -> {
lock.wait(); synchronized (lock) {
} catch (InterruptedException e) { } ready.release();
} try {
}); lock.wait();
var thread2 = Thread.ofVirtual().start(() -> { } catch (InterruptedException e) { }
ready.acquireUninterruptibly(); }
synchronized (lock) { });
lock.notifyAll(); var thread2 = Thread.ofVirtual().start(() -> {
} ready.acquireUninterruptibly();
}); synchronized (lock) {
thread1.join(); lock.notifyAll();
thread2.join(); }
});
thread1.join();
thread2.join();
} finally {
// restore
VThreadRunner.setParallelism(previousParallelism);
}
} }
/** /**

View File

@ -23,7 +23,7 @@
/** /**
* @test * @test
* @summary Test stack traces in exceptions and stack frames waslked by the StackWalker * @summary Test stack traces in exceptions and stack frames walked by the StackWalker
* API do not include the carrier stack frames * API do not include the carrier stack frames
* @requires vm.continuations * @requires vm.continuations
* @modules java.management * @modules java.management

View File

@ -27,7 +27,7 @@
* @summary Test Thread API with virtual threads * @summary Test Thread API with virtual threads
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib * @library /test/lib
* @run junit ThreadAPI * @run junit/othervm --enable-native-access=ALL-UNNAMED ThreadAPI
*/ */
/* /*
@ -35,7 +35,8 @@
* @requires vm.continuations * @requires vm.continuations
* @modules java.base/java.lang:+open * @modules java.base/java.lang:+open
* @library /test/lib * @library /test/lib
* @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ThreadAPI * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
* --enable-native-access=ALL-UNNAMED ThreadAPI
*/ */
import java.time.Duration; import java.time.Duration;
@ -61,6 +62,7 @@ import java.util.stream.Stream;
import java.nio.channels.Selector; import java.nio.channels.Selector;
import jdk.test.lib.thread.VThreadRunner; import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
@ -697,11 +699,11 @@ class ThreadAPI {
void testJoin33() throws Exception { void testJoin33() throws Exception {
AtomicBoolean done = new AtomicBoolean(); AtomicBoolean done = new AtomicBoolean();
Thread thread = Thread.ofVirtual().start(() -> { Thread thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { VThreadPinner.runPinned(() -> {
while (!done.get()) { while (!done.get()) {
LockSupport.parkNanos(Duration.ofMillis(20).toNanos()); LockSupport.parkNanos(Duration.ofMillis(20).toNanos());
} }
} });
}); });
try { try {
assertFalse(thread.join(Duration.ofMillis(100))); assertFalse(thread.join(Duration.ofMillis(100)));
@ -1078,7 +1080,7 @@ class ThreadAPI {
} }
/** /**
* Test Thread.yield releases thread when not pinned. * Test Thread.yield releases carrier thread.
*/ */
@Test @Test
void testYield1() throws Exception { void testYield1() throws Exception {
@ -1106,7 +1108,7 @@ class ThreadAPI {
} }
/** /**
* Test Thread.yield when thread is pinned. * Test Thread.yield when thread is pinned by native frame.
*/ */
@Test @Test
void testYield2() throws Exception { void testYield2() throws Exception {
@ -1121,10 +1123,10 @@ class ThreadAPI {
list.add("B"); list.add("B");
}); });
child.start(); child.start();
synchronized (lock) { VThreadPinner.runPinned(() -> {
Thread.yield(); // pinned so will be a no-op Thread.yield(); // pinned so will be a no-op
list.add("A"); list.add("A");
} });
try { child.join(); } catch (InterruptedException e) { } try { child.join(); } catch (InterruptedException e) { }
}); });
thread.start(); thread.start();
@ -1134,7 +1136,7 @@ class ThreadAPI {
} }
/** /**
* Test that Thread.yield does not consume the thread's parking permit. * Test Thread.yield does not consume the thread's parking permit.
*/ */
@Test @Test
void testYield3() throws Exception { void testYield3() throws Exception {
@ -1147,7 +1149,7 @@ class ThreadAPI {
} }
/** /**
* Test that Thread.yield does not make available the thread's parking permit. * Test Thread.yield does not make available the thread's parking permit.
*/ */
@Test @Test
void testYield4() throws Exception { void testYield4() throws Exception {
@ -1348,11 +1350,9 @@ class ThreadAPI {
*/ */
@Test @Test
void testSleep8() throws Exception { void testSleep8() throws Exception {
VThreadRunner.run(() -> { VThreadPinner.runPinned(() -> {
long start = millisTime(); long start = millisTime();
synchronized (lock) { Thread.sleep(1000);
Thread.sleep(1000);
}
expectDuration(start, /*min*/900, /*max*/20_000); expectDuration(start, /*min*/900, /*max*/20_000);
}); });
} }
@ -1366,9 +1366,9 @@ class ThreadAPI {
Thread me = Thread.currentThread(); Thread me = Thread.currentThread();
me.interrupt(); me.interrupt();
try { try {
synchronized (lock) { VThreadPinner.runPinned(() -> {
Thread.sleep(2000); Thread.sleep(2000);
} });
fail("sleep not interrupted"); fail("sleep not interrupted");
} catch (InterruptedException e) { } catch (InterruptedException e) {
// expected // expected
@ -1386,9 +1386,9 @@ class ThreadAPI {
Thread t = Thread.currentThread(); Thread t = Thread.currentThread();
scheduleInterrupt(t, 100); scheduleInterrupt(t, 100);
try { try {
synchronized (lock) { VThreadPinner.runPinned(() -> {
Thread.sleep(20 * 1000); Thread.sleep(20 * 1000);
} });
fail("sleep not interrupted"); fail("sleep not interrupted");
} catch (InterruptedException e) { } catch (InterruptedException e) {
// interrupt status should be cleared // interrupt status should be cleared
@ -1521,8 +1521,7 @@ class ThreadAPI {
@Test @Test
void testUncaughtExceptionHandler1() throws Exception { void testUncaughtExceptionHandler1() throws Exception {
class FooException extends RuntimeException { } class FooException extends RuntimeException { }
var exception = new AtomicReference<Throwable>(); var handler = new CapturingUHE();
Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
Thread thread = Thread.ofVirtual().start(() -> { Thread thread = Thread.ofVirtual().start(() -> {
Thread me = Thread.currentThread(); Thread me = Thread.currentThread();
assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup()); assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
@ -1531,7 +1530,8 @@ class ThreadAPI {
throw new FooException(); throw new FooException();
}); });
thread.join(); thread.join();
assertTrue(exception.get() instanceof FooException); assertInstanceOf(FooException.class, handler.exception());
assertEquals(thread, handler.thread());
assertNull(thread.getUncaughtExceptionHandler()); assertNull(thread.getUncaughtExceptionHandler());
} }
@ -1541,8 +1541,7 @@ class ThreadAPI {
@Test @Test
void testUncaughtExceptionHandler2() throws Exception { void testUncaughtExceptionHandler2() throws Exception {
class FooException extends RuntimeException { } class FooException extends RuntimeException { }
var exception = new AtomicReference<Throwable>(); var handler = new CapturingUHE();
Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(handler); Thread.setDefaultUncaughtExceptionHandler(handler);
Thread thread; Thread thread;
@ -1553,25 +1552,61 @@ class ThreadAPI {
}); });
thread.join(); thread.join();
} finally { } finally {
Thread.setDefaultUncaughtExceptionHandler(savedHandler); Thread.setDefaultUncaughtExceptionHandler(savedHandler); // restore
} }
assertTrue(exception.get() instanceof FooException); assertInstanceOf(FooException.class, handler.exception());
assertEquals(thread, handler.thread());
assertNull(thread.getUncaughtExceptionHandler()); assertNull(thread.getUncaughtExceptionHandler());
} }
/** /**
* Test no UncaughtExceptionHandler set. * Test Thread and default UncaughtExceptionHandler set.
*/ */
@Test @Test
void testUncaughtExceptionHandler3() throws Exception { void testUncaughtExceptionHandler3() throws Exception {
class FooException extends RuntimeException { } class FooException extends RuntimeException { }
Thread thread = Thread.ofVirtual().start(() -> { var defaultHandler = new CapturingUHE();
throw new FooException(); var threadHandler = new CapturingUHE();
}); Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
thread.join(); Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
Thread thread;
try {
thread = Thread.ofVirtual().start(() -> {
Thread me = Thread.currentThread();
assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
me.setUncaughtExceptionHandler(threadHandler);
assertTrue(me.getUncaughtExceptionHandler() == threadHandler);
throw new FooException();
});
thread.join();
} finally {
Thread.setDefaultUncaughtExceptionHandler(savedHandler); // restore
}
assertInstanceOf(FooException.class, threadHandler.exception());
assertNull(defaultHandler.exception());
assertEquals(thread, threadHandler.thread());
assertNull(thread.getUncaughtExceptionHandler()); assertNull(thread.getUncaughtExceptionHandler());
} }
/**
* Test no Thread or default UncaughtExceptionHandler set.
*/
@Test
void testUncaughtExceptionHandler4() throws Exception {
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(null);
try {
class FooException extends RuntimeException { }
Thread thread = Thread.ofVirtual().start(() -> {
throw new FooException();
});
thread.join();
assertNull(thread.getUncaughtExceptionHandler());
} finally {
Thread.setDefaultUncaughtExceptionHandler(savedHandler);
}
}
/** /**
* Test Thread::threadId and getId. * Test Thread::threadId and getId.
*/ */
@ -2006,10 +2041,76 @@ class ThreadAPI {
} }
/** /**
* Test Thread::getStackTrace on terminated thread. * Test Thread::getStackTrace on timed-parked thread.
*/ */
@Test @Test
void testGetStackTrace6() throws Exception { 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();
}
}
/**
* Test Thread::getStackTrace on parked thread that is pinned.
*/
@Test
void testGetStackTrace7() throws Exception {
AtomicBoolean done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
while (!done.get()) {
LockSupport.park();
}
});
});
await(thread, Thread.State.WAITING);
try {
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.park"));
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test Thread::getStackTrace on timed-parked thread that is pinned.
*/
@Test
void testGetStackTrace8() throws Exception {
AtomicBoolean done = new AtomicBoolean();
var thread = Thread.ofVirtual().start(() -> {
VThreadPinner.runPinned(() -> {
while (!done.get()) {
LockSupport.parkNanos(Long.MAX_VALUE);
}
});
});
await(thread, Thread.State.TIMED_WAITING);
try {
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.parkNanos"));
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test Thread::getStackTrace on terminated thread.
*/
@Test
void testGetStackTrace9() throws Exception {
var thread = Thread.ofVirtual().start(() -> { }); var thread = Thread.ofVirtual().start(() -> { });
thread.join(); thread.join();
StackTraceElement[] stack = thread.getStackTrace(); StackTraceElement[] stack = thread.getStackTrace();
@ -2176,7 +2277,7 @@ class ThreadAPI {
ThreadGroup vgroup = Thread.currentThread().getThreadGroup(); ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[100]; Thread[] threads = new Thread[100];
int n = vgroup.enumerate(threads, /*recurse*/false); int n = vgroup.enumerate(threads, /*recurse*/false);
assertTrue(n == 0); assertFalse(Arrays.stream(threads, 0, n).anyMatch(Thread::isVirtual));
}); });
} }
@ -2289,6 +2390,33 @@ class ThreadAPI {
assertTrue(thread.toString().contains("fred")); assertTrue(thread.toString().contains("fred"));
} }
/**
* Thread.UncaughtExceptionHandler that captures the first exception thrown.
*/
private static class CapturingUHE implements Thread.UncaughtExceptionHandler {
Thread thread;
Throwable exception;
@Override
public void uncaughtException(Thread t, Throwable e) {
synchronized (this) {
if (thread == null) {
this.thread = t;
this.exception = e;
}
}
}
Thread thread() {
synchronized (this) {
return thread;
}
}
Throwable exception() {
synchronized (this) {
return exception;
}
}
}
/** /**
* Waits for the given thread to reach a given state. * Waits for the given thread to reach a given state.
*/ */

View File

@ -25,15 +25,18 @@
* @test * @test
* @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws * @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws
* @modules java.base/jdk.internal.event * @modules java.base/jdk.internal.event
* @library /test/lib
* @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java * @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java
* @run junit VirtualThreadPinnedEventThrows * @run junit/othervm --enable-native-access=ALL-UNNAMED VirtualThreadPinnedEventThrows
*/ */
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import jdk.internal.event.VirtualThreadPinnedEvent; import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -82,29 +85,31 @@ class VirtualThreadPinnedEventThrows {
* Test parking a virtual thread when pinned. * Test parking a virtual thread when pinned.
*/ */
private void testParkWhenPinned() throws Exception { private void testParkWhenPinned() throws Exception {
Object lock = new Object(); var exception = new AtomicReference<Throwable>();
var done = new AtomicBoolean();
Thread thread = Thread.startVirtualThread(() -> {
try {
VThreadPinner.runPinned(() -> {
while (!done.get()) {
LockSupport.park();
}
});
} catch (Throwable e) {
exception.set(e);
}
});
try { try {
var completed = new AtomicBoolean();
Thread thread = Thread.startVirtualThread(() -> {
synchronized (lock) {
LockSupport.park();
completed.set(true);
}
});
// wait for thread to park // wait for thread to park
Thread.State state; Thread.State state;
while ((state = thread.getState()) != Thread.State.WAITING) { while ((state = thread.getState()) != Thread.State.WAITING) {
assertTrue(state != Thread.State.TERMINATED); assertTrue(state != Thread.State.TERMINATED);
Thread.sleep(10); Thread.sleep(10);
} }
} finally {
// unpark and check that thread completed without exception done.set(true);
LockSupport.unpark(thread); LockSupport.unpark(thread);
thread.join(); thread.join();
assertTrue(completed.get());
} finally {
Reference.reachabilityFence(lock);
} }
assertNull(exception.get());
} }
} }

View File

@ -25,13 +25,15 @@
* @test * @test
* @summary Stress test timed park when pinned * @summary Stress test timed park when pinned
* @requires vm.debug != true * @requires vm.debug != true
* @run main PinALot 500000 * @library /test/lib
* @run main/othervm --enable-native-access=ALL-UNNAMED PinALot 500000
*/ */
/* /*
* @test * @test
* @requires vm.debug == true * @requires vm.debug == true
* @run main/othervm/timeout=300 PinALot 200000 * @library /test/lib
* @run main/othervm/timeout=300 --enable-native-access=ALL-UNNAMED PinALot 200000
*/ */
import java.time.Duration; import java.time.Duration;
@ -39,9 +41,9 @@ import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
public class PinALot { import jdk.test.lib.thread.VThreadPinner;
static final Object lock = new Object(); public class PinALot {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
int iterations = 1_000_000; int iterations = 1_000_000;
@ -53,11 +55,11 @@ public class PinALot {
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> { Thread thread = Thread.ofVirtual().start(() -> {
synchronized (lock) { VThreadPinner.runPinned(() -> {
while (count.incrementAndGet() < ITERATIONS) { while (count.incrementAndGet() < ITERATIONS) {
LockSupport.parkNanos(1); LockSupport.parkNanos(1);
} }
} });
}); });
boolean terminated; boolean terminated;

View File

@ -26,7 +26,7 @@
* @summary Stress test virtual threads with a variation of the Skynet 1M benchmark * @summary Stress test virtual threads with a variation of the Skynet 1M benchmark
* @requires vm.continuations * @requires vm.continuations
* @requires !vm.debug | vm.gc != "Z" * @requires !vm.debug | vm.gc != "Z"
* @run main/othervm/timeout=300 -Xmx1g Skynet * @run main/othervm/timeout=300 -Xmx1500m Skynet
*/ */
/* /*
@ -35,7 +35,7 @@
* @requires vm.gc.ZSinglegen * @requires vm.gc.ZSinglegen
* @run main/othervm/timeout=300 -XX:+UnlockDiagnosticVMOptions * @run main/othervm/timeout=300 -XX:+UnlockDiagnosticVMOptions
* -XX:+UseZGC -XX:-ZGenerational * -XX:+UseZGC -XX:-ZGenerational
* -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1g Skynet * -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1500m Skynet
*/ */
/* /*
@ -44,7 +44,7 @@
* @requires vm.gc.ZGenerational * @requires vm.gc.ZGenerational
* @run main/othervm/timeout=300 -XX:+UnlockDiagnosticVMOptions * @run main/othervm/timeout=300 -XX:+UnlockDiagnosticVMOptions
* -XX:+UseZGC -XX:+ZGenerational * -XX:+UseZGC -XX:+ZGenerational
* -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1g Skynet * -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1500m Skynet
*/ */
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -411,9 +411,7 @@ public class SelectWithConsumer {
// select(Consumer, timeout) // select(Consumer, timeout)
try (Selector sel = Selector.open()) { try (Selector sel = Selector.open()) {
scheduleInterrupt(Thread.currentThread(), 1, SECONDS); scheduleInterrupt(Thread.currentThread(), 1, SECONDS);
long start = System.currentTimeMillis();
int n = sel.select(k -> assertTrue(false), 60*1000); int n = sel.select(k -> assertTrue(false), 60*1000);
long duration = System.currentTimeMillis() - start;
assertTrue(n == 0); assertTrue(n == 0);
assertTrue(Thread.currentThread().isInterrupted()); assertTrue(Thread.currentThread().isInterrupted());
assertTrue(sel.isOpen()); assertTrue(sel.isOpen());

View File

@ -0,0 +1,151 @@
/*
* 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.
*/
package jdk.test.lib.thread;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
import jdk.test.lib.thread.VThreadRunner.ThrowingRunnable;
/**
* Helper class to allow tests run a task in a virtual thread while pinning its carrier.
*
* It defines the {@code runPinned} method to run a task with a native frame on the stack.
*/
public class VThreadPinner {
private static final Path JAVA_LIBRARY_PATH = Path.of(System.getProperty("java.library.path"));
private static final Path LIB_PATH = JAVA_LIBRARY_PATH.resolve(System.mapLibraryName("VThreadPinner"));
// method handle to call the native function
private static final MethodHandle INVOKER = invoker();
// function pointer to call
private static final MemorySegment UPCALL_STUB = upcallStub();
/**
* Thread local with the task to run.
*/
private static final ThreadLocal<TaskRunner> TASK_RUNNER = new ThreadLocal<>();
/**
* Runs a task, capturing any exception or error thrown.
*/
private static class TaskRunner implements Runnable {
private final ThrowingRunnable<?> task;
private Throwable throwable;
TaskRunner(ThrowingRunnable<?> task) {
this.task = task;
}
@Override
public void run() {
try {
task.run();
} catch (Throwable ex) {
throwable = ex;
}
}
Throwable exception() {
return throwable;
}
}
/**
* Called by the native function to run the task stashed in the thread local. The
* task runs with the native frame on the stack.
*/
private static void callback() {
TASK_RUNNER.get().run();
}
/**
* Runs the given task on a virtual thread pinned to its carrier. If called from a
* virtual thread then it invokes the task directly.
*/
public static <X extends Throwable> void runPinned(ThrowingRunnable<X> task) throws X {
if (!Thread.currentThread().isVirtual()) {
VThreadRunner.run(() -> runPinned(task));
return;
}
var runner = new TaskRunner(task);
TASK_RUNNER.set(runner);
try {
INVOKER.invoke(UPCALL_STUB);
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
TASK_RUNNER.remove();
}
Throwable ex = runner.exception();
if (ex != null) {
if (ex instanceof RuntimeException e)
throw e;
if (ex instanceof Error e)
throw e;
throw (X) ex;
}
}
/**
* Returns a method handle to the native function void call(void *(*f)(void *)).
*/
@SuppressWarnings("restricted")
private static MethodHandle invoker() {
Linker abi = Linker.nativeLinker();
try {
SymbolLookup lib = SymbolLookup.libraryLookup(LIB_PATH, Arena.global());
MemorySegment symbol = lib.find("call").orElseThrow();
FunctionDescriptor desc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS);
return abi.downcallHandle(symbol, desc);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* Returns an upcall stub to use as a function pointer to invoke the callback method.
*/
@SuppressWarnings("restricted")
private static MemorySegment upcallStub() {
Linker abi = Linker.nativeLinker();
try {
MethodHandle callback = MethodHandles.lookup()
.findStatic(VThreadPinner.class, "callback", MethodType.methodType(void.class));
return abi.upcallStub(callback, FunctionDescriptor.ofVoid(), Arena.global());
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}

View File

@ -29,7 +29,7 @@ import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
* Helper class to support tests running tasks a in virtual thread. * Helper class to support tests running tasks in a virtual thread.
*/ */
public class VThreadRunner { public class VThreadRunner {
private VThreadRunner() { } private VThreadRunner() { }
@ -41,38 +41,31 @@ public class VThreadRunner {
public static final int NO_INHERIT_THREAD_LOCALS = 1 << 2; public static final int NO_INHERIT_THREAD_LOCALS = 1 << 2;
/** /**
* Represents a task that does not return a result but may throw * Represents a task that does not return a result but may throw an exception.
* an exception.
*/ */
@FunctionalInterface @FunctionalInterface
public interface ThrowingRunnable { public interface ThrowingRunnable<X extends Throwable> {
/** void run() throws X;
* Runs this operation.
*/
void run() throws Exception;
} }
/** /**
* Run a task in a virtual thread and wait for it to terminate. * Run a task in a virtual thread and wait for it to terminate.
* If the task completes with an exception then it is thrown by this method. * If the task completes with an exception then it is thrown by this method.
* If the task throws an Error then it is wrapped in an RuntimeException.
* *
* @param name thread name, can be null * @param name thread name, can be null
* @param characteristics thread characteristics * @param characteristics thread characteristics
* @param task the task to run * @param task the task to run
* @throws Exception the exception thrown by the task * @throws X the exception thrown by the task
*/ */
public static void run(String name, public static <X extends Throwable> void run(String name,
int characteristics, int characteristics,
ThrowingRunnable task) throws Exception { ThrowingRunnable<X> task) throws X {
AtomicReference<Exception> exc = new AtomicReference<>(); var throwableRef = new AtomicReference<Throwable>();
Runnable target = () -> { Runnable target = () -> {
try { try {
task.run(); task.run();
} catch (Error e) { } catch (Throwable ex) {
exc.set(new RuntimeException(e)); throwableRef.set(ex);
} catch (Exception e) {
exc.set(e);
} }
}; };
@ -84,54 +77,59 @@ public class VThreadRunner {
Thread thread = builder.start(target); Thread thread = builder.start(target);
// wait for thread to terminate // wait for thread to terminate
while (thread.join(Duration.ofSeconds(10)) == false) { try {
System.out.println("-- " + thread + " --"); while (thread.join(Duration.ofSeconds(10)) == false) {
for (StackTraceElement e : thread.getStackTrace()) { System.out.println("-- " + thread + " --");
System.out.println(" " + e); for (StackTraceElement e : thread.getStackTrace()) {
System.out.println(" " + e);
}
} }
} catch (InterruptedException e) {
throw new RuntimeException(e);
} }
Exception e = exc.get(); Throwable ex = throwableRef.get();
if (e != null) { if (ex != null) {
throw e; if (ex instanceof RuntimeException e)
throw e;
if (ex instanceof Error e)
throw e;
throw (X) ex;
} }
} }
/** /**
* Run a task in a virtual thread and wait for it to terminate. * Run a task in a virtual thread and wait for it to terminate.
* If the task completes with an exception then it is thrown by this method. * If the task completes with an exception then it is thrown by this method.
* If the task throws an Error then it is wrapped in an RuntimeException.
* *
* @param name thread name, can be null * @param name thread name, can be null
* @param task the task to run * @param task the task to run
* @throws Exception the exception thrown by the task * @throws X the exception thrown by the task
*/ */
public static void run(String name, ThrowingRunnable task) throws Exception { public static <X extends Throwable> void run(String name, ThrowingRunnable<X> task) throws X {
run(name, 0, task); run(name, 0, task);
} }
/** /**
* Run a task in a virtual thread and wait for it to terminate. * Run a task in a virtual thread and wait for it to terminate.
* If the task completes with an exception then it is thrown by this method. * If the task completes with an exception then it is thrown by this method.
* If the task throws an Error then it is wrapped in an RuntimeException.
* *
* @param characteristics thread characteristics * @param characteristics thread characteristics
* @param task the task to run * @param task the task to run
* @throws Exception the exception thrown by the task * @throws X the exception thrown by the task
*/ */
public static void run(int characteristics, ThrowingRunnable task) throws Exception { public static <X extends Throwable> void run(int characteristics, ThrowingRunnable<X> task) throws X {
run(null, characteristics, task); run(null, characteristics, task);
} }
/** /**
* Run a task in a virtual thread and wait for it to terminate. * Run a task in a virtual thread and wait for it to terminate.
* If the task completes with an exception then it is thrown by this method. * If the task completes with an exception then it is thrown by this method.
* If the task throws an Error then it is wrapped in an RuntimeException.
* *
* @param task the task to run * @param task the task to run
* @throws Exception the exception thrown by the task * @throws X the exception thrown by the task
*/ */
public static void run(ThrowingRunnable task) throws Exception { public static <X extends Throwable> void run(ThrowingRunnable<X> task) throws X {
run(null, 0, task); run(null, 0, task);
} }

View File

@ -0,0 +1,37 @@
/*
* 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 <stdio.h>
#ifdef _WIN64
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif
/*
* Call a function with the given function pointer.
*/
EXPORT void call(void *(*f)(void)) {
(*f)();
}