8320707: Virtual thread test updates
Reviewed-by: jpai
This commit is contained in:
parent
7eb25ec7b3
commit
b67b71cd87
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
151
test/lib/jdk/test/lib/thread/VThreadPinner.java
Normal file
151
test/lib/jdk/test/lib/thread/VThreadPinner.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
37
test/lib/jdk/test/lib/thread/libVThreadPinner.c
Normal file
37
test/lib/jdk/test/lib/thread/libVThreadPinner.c
Normal 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)();
|
||||
}
|
Loading…
Reference in New Issue
Block a user