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

View File

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

View File

@ -29,35 +29,34 @@
*/
import java.io.IOException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.Selector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class GetStackTraceWhenRunnable {
public static void main(String[] args) throws Exception {
try (Selector sel = Selector.open()) {
// start thread1 and wait for it to park
Thread thread1 = Thread.startVirtualThread(LockSupport::park);
while (thread1.getState() != Thread.State.WAITING) {
Thread.sleep(20);
// start thread1 and wait for it to park
Thread thread1 = Thread.startVirtualThread(LockSupport::park);
while (thread1.getState() != Thread.State.WAITING) {
Thread.sleep(20);
}
// start thread2 to pin the carrier thread
var started = new AtomicBoolean();
var done = new AtomicBoolean();
Thread thread2 = Thread.startVirtualThread(() -> {
started.set(true);
while (!done.get()) {
Thread.onSpinWait();
}
});
try {
// wait for thread2 to start
while (!started.get()) {
Thread.sleep(10);
}
// 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
// (the carrier thread is pinned, no other virtual thread can run)
@ -73,6 +72,10 @@ public class GetStackTraceWhenRunnable {
for (StackTraceElement e : stack) {
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
* @requires vm.continuations
* @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.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@ -39,20 +39,27 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jfr.EventType;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
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.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
class JfrEvents {
private static final Object lock = new Object();
/**
* 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
void testVirtualThreadPinned() throws Exception {
Runnable[] parkers = new Runnable[] {
() -> LockSupport.park(),
() -> LockSupport.parkNanos(Duration.ofDays(1).toNanos())
};
@ParameterizedTest
@MethodSource("pinnedCases")
void testVirtualThreadPinned(String label,
ThrowingRunnable<Exception> parker,
Thread.State expectedState,
Consumer<Thread> unparker) throws Exception {
try (Recording recording = new Recording()) {
recording.enable("jdk.VirtualThreadPinned");
recording.start();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (Runnable parker : parkers) {
// execute parking task in virtual thread
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 {
var exception = new AtomicReference<Throwable>();
var thread = Thread.ofVirtual().start(() -> {
try {
Thread.State state = thread.getState();
while (state != Thread.State.WAITING && state != Thread.State.TIMED_WAITING) {
Thread.sleep(10);
state = thread.getState();
}
} finally {
LockSupport.unpark(thread);
parker.run();
} catch (Throwable e) {
exception.set(e);
}
});
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 {
recording.stop();
@ -132,9 +184,9 @@ class JfrEvents {
Map<String, Integer> events = sumEvents(recording);
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);
assertEquals(parkers.length, pinnedCount);
assertTrue(pinnedCount >= 1, "Expected one or more events");
}
}

View File

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

View File

@ -23,7 +23,7 @@
/**
* @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
* @requires vm.continuations
* @modules java.management

View File

@ -27,7 +27,7 @@
* @summary Test Thread API with virtual threads
* @modules java.base/java.lang:+open
* @library /test/lib
* @run junit ThreadAPI
* @run junit/othervm --enable-native-access=ALL-UNNAMED ThreadAPI
*/
/*
@ -35,7 +35,8 @@
* @requires vm.continuations
* @modules java.base/java.lang:+open
* @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;
@ -61,6 +62,7 @@ import java.util.stream.Stream;
import java.nio.channels.Selector;
import jdk.test.lib.thread.VThreadRunner;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
@ -697,11 +699,11 @@ class ThreadAPI {
void testJoin33() throws Exception {
AtomicBoolean done = new AtomicBoolean();
Thread thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
VThreadPinner.runPinned(() -> {
while (!done.get()) {
LockSupport.parkNanos(Duration.ofMillis(20).toNanos());
}
}
});
});
try {
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
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
void testYield2() throws Exception {
@ -1121,10 +1123,10 @@ class ThreadAPI {
list.add("B");
});
child.start();
synchronized (lock) {
VThreadPinner.runPinned(() -> {
Thread.yield(); // pinned so will be a no-op
list.add("A");
}
});
try { child.join(); } catch (InterruptedException e) { }
});
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
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
void testYield4() throws Exception {
@ -1348,11 +1350,9 @@ class ThreadAPI {
*/
@Test
void testSleep8() throws Exception {
VThreadRunner.run(() -> {
VThreadPinner.runPinned(() -> {
long start = millisTime();
synchronized (lock) {
Thread.sleep(1000);
}
Thread.sleep(1000);
expectDuration(start, /*min*/900, /*max*/20_000);
});
}
@ -1366,9 +1366,9 @@ class ThreadAPI {
Thread me = Thread.currentThread();
me.interrupt();
try {
synchronized (lock) {
VThreadPinner.runPinned(() -> {
Thread.sleep(2000);
}
});
fail("sleep not interrupted");
} catch (InterruptedException e) {
// expected
@ -1386,9 +1386,9 @@ class ThreadAPI {
Thread t = Thread.currentThread();
scheduleInterrupt(t, 100);
try {
synchronized (lock) {
VThreadPinner.runPinned(() -> {
Thread.sleep(20 * 1000);
}
});
fail("sleep not interrupted");
} catch (InterruptedException e) {
// interrupt status should be cleared
@ -1521,8 +1521,7 @@ class ThreadAPI {
@Test
void testUncaughtExceptionHandler1() throws Exception {
class FooException extends RuntimeException { }
var exception = new AtomicReference<Throwable>();
Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
var handler = new CapturingUHE();
Thread thread = Thread.ofVirtual().start(() -> {
Thread me = Thread.currentThread();
assertTrue(me.getUncaughtExceptionHandler() == me.getThreadGroup());
@ -1531,7 +1530,8 @@ class ThreadAPI {
throw new FooException();
});
thread.join();
assertTrue(exception.get() instanceof FooException);
assertInstanceOf(FooException.class, handler.exception());
assertEquals(thread, handler.thread());
assertNull(thread.getUncaughtExceptionHandler());
}
@ -1541,8 +1541,7 @@ class ThreadAPI {
@Test
void testUncaughtExceptionHandler2() throws Exception {
class FooException extends RuntimeException { }
var exception = new AtomicReference<Throwable>();
Thread.UncaughtExceptionHandler handler = (thread, exc) -> exception.set(exc);
var handler = new CapturingUHE();
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(handler);
Thread thread;
@ -1553,25 +1552,61 @@ class ThreadAPI {
});
thread.join();
} 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());
}
/**
* Test no UncaughtExceptionHandler set.
* Test Thread and default UncaughtExceptionHandler set.
*/
@Test
void testUncaughtExceptionHandler3() throws Exception {
class FooException extends RuntimeException { }
Thread thread = Thread.ofVirtual().start(() -> {
throw new FooException();
});
thread.join();
var defaultHandler = new CapturingUHE();
var threadHandler = new CapturingUHE();
Thread.UncaughtExceptionHandler savedHandler = Thread.getDefaultUncaughtExceptionHandler();
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());
}
/**
* 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.
*/
@ -2006,10 +2041,76 @@ class ThreadAPI {
}
/**
* Test Thread::getStackTrace on terminated thread.
* Test Thread::getStackTrace on timed-parked thread.
*/
@Test
void testGetStackTrace6() throws Exception {
var thread = Thread.ofVirtual().start(() -> {
LockSupport.parkNanos(Long.MAX_VALUE);
});
await(thread, Thread.State.TIMED_WAITING);
try {
StackTraceElement[] stack = thread.getStackTrace();
assertTrue(contains(stack, "LockSupport.parkNanos"));
} finally {
LockSupport.unpark(thread);
thread.join();
}
}
/**
* 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(() -> { });
thread.join();
StackTraceElement[] stack = thread.getStackTrace();
@ -2176,7 +2277,7 @@ class ThreadAPI {
ThreadGroup vgroup = Thread.currentThread().getThreadGroup();
Thread[] threads = new Thread[100];
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"));
}
/**
* 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.
*/

View File

@ -25,15 +25,18 @@
* @test
* @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws
* @modules java.base/jdk.internal.event
* @library /test/lib
* @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.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.test.lib.thread.VThreadPinner;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@ -82,29 +85,31 @@ class VirtualThreadPinnedEventThrows {
* Test parking a virtual thread when pinned.
*/
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 {
var completed = new AtomicBoolean();
Thread thread = Thread.startVirtualThread(() -> {
synchronized (lock) {
LockSupport.park();
completed.set(true);
}
});
// wait for thread to park
Thread.State state;
while ((state = thread.getState()) != Thread.State.WAITING) {
assertTrue(state != Thread.State.TERMINATED);
Thread.sleep(10);
}
// unpark and check that thread completed without exception
} finally {
done.set(true);
LockSupport.unpark(thread);
thread.join();
assertTrue(completed.get());
} finally {
Reference.reachabilityFence(lock);
}
assertNull(exception.get());
}
}

View File

@ -25,13 +25,15 @@
* @test
* @summary Stress test timed park when pinned
* @requires vm.debug != true
* @run main PinALot 500000
* @library /test/lib
* @run main/othervm --enable-native-access=ALL-UNNAMED PinALot 500000
*/
/*
* @test
* @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;
@ -39,9 +41,9 @@ import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
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 {
int iterations = 1_000_000;
@ -53,11 +55,11 @@ public class PinALot {
AtomicInteger count = new AtomicInteger();
Thread thread = Thread.ofVirtual().start(() -> {
synchronized (lock) {
VThreadPinner.runPinned(() -> {
while (count.incrementAndGet() < ITERATIONS) {
LockSupport.parkNanos(1);
}
}
});
});
boolean terminated;

View File

@ -26,7 +26,7 @@
* @summary Stress test virtual threads with a variation of the Skynet 1M benchmark
* @requires vm.continuations
* @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
* @run main/othervm/timeout=300 -XX:+UnlockDiagnosticVMOptions
* -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
* @run main/othervm/timeout=300 -XX:+UnlockDiagnosticVMOptions
* -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;

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.
*
* This code is free software; you can redistribute it and/or modify it
@ -411,9 +411,7 @@ public class SelectWithConsumer {
// select(Consumer, timeout)
try (Selector sel = Selector.open()) {
scheduleInterrupt(Thread.currentThread(), 1, SECONDS);
long start = System.currentTimeMillis();
int n = sel.select(k -> assertTrue(false), 60*1000);
long duration = System.currentTimeMillis() - start;
assertTrue(n == 0);
assertTrue(Thread.currentThread().isInterrupted());
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;
/**
* 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 {
private VThreadRunner() { }
@ -41,38 +41,31 @@ public class VThreadRunner {
public static final int NO_INHERIT_THREAD_LOCALS = 1 << 2;
/**
* Represents a task that does not return a result but may throw
* an exception.
* Represents a task that does not return a result but may throw an exception.
*/
@FunctionalInterface
public interface ThrowingRunnable {
/**
* Runs this operation.
*/
void run() throws Exception;
public interface ThrowingRunnable<X extends Throwable> {
void run() throws X;
}
/**
* 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 throws an Error then it is wrapped in an RuntimeException.
*
* @param name thread name, can be null
* @param characteristics thread characteristics
* @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,
int characteristics,
ThrowingRunnable task) throws Exception {
AtomicReference<Exception> exc = new AtomicReference<>();
Runnable target = () -> {
public static <X extends Throwable> void run(String name,
int characteristics,
ThrowingRunnable<X> task) throws X {
var throwableRef = new AtomicReference<Throwable>();
Runnable target = () -> {
try {
task.run();
} catch (Error e) {
exc.set(new RuntimeException(e));
} catch (Exception e) {
exc.set(e);
} catch (Throwable ex) {
throwableRef.set(ex);
}
};
@ -84,54 +77,59 @@ public class VThreadRunner {
Thread thread = builder.start(target);
// wait for thread to terminate
while (thread.join(Duration.ofSeconds(10)) == false) {
System.out.println("-- " + thread + " --");
for (StackTraceElement e : thread.getStackTrace()) {
System.out.println(" " + e);
try {
while (thread.join(Duration.ofSeconds(10)) == false) {
System.out.println("-- " + thread + " --");
for (StackTraceElement e : thread.getStackTrace()) {
System.out.println(" " + e);
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Exception e = exc.get();
if (e != null) {
throw e;
Throwable ex = throwableRef.get();
if (ex != null) {
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.
* 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 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 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 throws an Error then it is wrapped in an RuntimeException.
*
* @param characteristics thread characteristics
* @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 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 throws an Error then it is wrapped in an RuntimeException.
*
* @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);
}

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)();
}