From b67b71cd87c62f15d5b73f923c300d0f77c988f5 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Wed, 3 Jan 2024 14:59:03 +0000 Subject: [PATCH] 8320707: Virtual thread test updates Reviewed-by: jpai --- .../GetThreadState/GetThreadStateTest.java | 79 +++---- .../lang/ScopedValue/StressStackOverflow.java | 2 +- .../Thread/virtual/CarrierThreadWaits.java | 42 ++-- .../virtual/GetStackTraceWhenRunnable.java | 45 ++-- .../java/lang/Thread/virtual/JfrEvents.java | 120 +++++++---- ...WaitNotify.java => MonitorWaitNotify.java} | 48 +++-- .../java/lang/Thread/virtual/StackTraces.java | 2 +- .../java/lang/Thread/virtual/ThreadAPI.java | 192 +++++++++++++++--- .../VirtualThreadPinnedEventThrows.java | 35 ++-- .../lang/Thread/virtual/stress/PinALot.java | 14 +- .../lang/Thread/virtual/stress/Skynet.java | 6 +- .../channels/Selector/SelectWithConsumer.java | 4 +- .../jdk/test/lib/thread/VThreadPinner.java | 151 ++++++++++++++ .../jdk/test/lib/thread/VThreadRunner.java | 68 +++---- .../jdk/test/lib/thread/libVThreadPinner.c | 37 ++++ 15 files changed, 625 insertions(+), 220 deletions(-) rename test/jdk/java/lang/Thread/virtual/{WaitNotify.java => MonitorWaitNotify.java} (81%) create mode 100644 test/lib/jdk/test/lib/thread/VThreadPinner.java create mode 100644 test/lib/jdk/test/lib/thread/libVThreadPinner.c diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java index 17c17b9b1f5..4ee1d1a0bfc 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java @@ -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. */ diff --git a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java index a1254c7fef7..863ad189c35 100644 --- a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java +++ b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java @@ -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. diff --git a/test/jdk/java/lang/Thread/virtual/CarrierThreadWaits.java b/test/jdk/java/lang/Thread/virtual/CarrierThreadWaits.java index a94a5de3ce0..d1d5e72204f 100644 --- a/test/jdk/java/lang/Thread/virtual/CarrierThreadWaits.java +++ b/test/jdk/java/lang/Thread/virtual/CarrierThreadWaits.java @@ -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(); + var vthreadRef = new AtomicReference(); + 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); } diff --git a/test/jdk/java/lang/Thread/virtual/GetStackTraceWhenRunnable.java b/test/jdk/java/lang/Thread/virtual/GetStackTraceWhenRunnable.java index 4cf7deecb36..38760eb52a8 100644 --- a/test/jdk/java/lang/Thread/virtual/GetStackTraceWhenRunnable.java +++ b/test/jdk/java/lang/Thread/virtual/GetStackTraceWhenRunnable.java @@ -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(); } } diff --git a/test/jdk/java/lang/Thread/virtual/JfrEvents.java b/test/jdk/java/lang/Thread/virtual/JfrEvents.java index 0cdd6a529d1..282a8959fe8 100644 --- a/test/jdk/java/lang/Thread/virtual/JfrEvents.java +++ b/test/jdk/java/lang/Thread/virtual/JfrEvents.java @@ -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 pinnedCases() { + Object lock = new Object(); + + // park with native frame on stack + var finish1 = new AtomicBoolean(); + var parkWhenPinned = Arguments.of( + "LockSupport.park when pinned", + (ThrowingRunnable) () -> { + VThreadPinner.runPinned(() -> { + while (!finish1.get()) { + LockSupport.park(); + } + }); + }, + Thread.State.WAITING, + (Consumer) 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) () -> { + VThreadPinner.runPinned(() -> { + while (!finish2.get()) { + LockSupport.parkNanos(Long.MAX_VALUE); + } + }); + }, + Thread.State.TIMED_WAITING, + (Consumer) 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 parker, + Thread.State expectedState, + Consumer 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(); - 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(); + 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 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"); } } diff --git a/test/jdk/java/lang/Thread/virtual/WaitNotify.java b/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java similarity index 81% rename from test/jdk/java/lang/Thread/virtual/WaitNotify.java rename to test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java index bc7b36b39be..1320cc8f15f 100644 --- a/test/jdk/java/lang/Thread/virtual/WaitNotify.java +++ b/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java @@ -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); + } } /** diff --git a/test/jdk/java/lang/Thread/virtual/StackTraces.java b/test/jdk/java/lang/Thread/virtual/StackTraces.java index fb5c40dc4e9..cb1877090fe 100644 --- a/test/jdk/java/lang/Thread/virtual/StackTraces.java +++ b/test/jdk/java/lang/Thread/virtual/StackTraces.java @@ -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 diff --git a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java index 3371b5bc9e7..b1ccb21910a 100644 --- a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java +++ b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java @@ -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(); - 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(); - 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. */ diff --git a/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java b/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java index a4bccd47265..1ae73b280cb 100644 --- a/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java +++ b/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java @@ -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(); + 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()); } } diff --git a/test/jdk/java/lang/Thread/virtual/stress/PinALot.java b/test/jdk/java/lang/Thread/virtual/stress/PinALot.java index 7bfce95b5c2..a420ebb330e 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/PinALot.java +++ b/test/jdk/java/lang/Thread/virtual/stress/PinALot.java @@ -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; diff --git a/test/jdk/java/lang/Thread/virtual/stress/Skynet.java b/test/jdk/java/lang/Thread/virtual/stress/Skynet.java index fb4adbb25f2..ee45fc827b0 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/Skynet.java +++ b/test/jdk/java/lang/Thread/virtual/stress/Skynet.java @@ -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; diff --git a/test/jdk/java/nio/channels/Selector/SelectWithConsumer.java b/test/jdk/java/nio/channels/Selector/SelectWithConsumer.java index 630a1ef88b8..de8b48ec22d 100644 --- a/test/jdk/java/nio/channels/Selector/SelectWithConsumer.java +++ b/test/jdk/java/nio/channels/Selector/SelectWithConsumer.java @@ -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()); diff --git a/test/lib/jdk/test/lib/thread/VThreadPinner.java b/test/lib/jdk/test/lib/thread/VThreadPinner.java new file mode 100644 index 00000000000..25b06912fdf --- /dev/null +++ b/test/lib/jdk/test/lib/thread/VThreadPinner.java @@ -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 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 void runPinned(ThrowingRunnable 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); + } + } +} diff --git a/test/lib/jdk/test/lib/thread/VThreadRunner.java b/test/lib/jdk/test/lib/thread/VThreadRunner.java index 74158c89a14..ba69496a047 100644 --- a/test/lib/jdk/test/lib/thread/VThreadRunner.java +++ b/test/lib/jdk/test/lib/thread/VThreadRunner.java @@ -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 { + 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 exc = new AtomicReference<>(); - Runnable target = () -> { + public static void run(String name, + int characteristics, + ThrowingRunnable task) throws X { + var throwableRef = new AtomicReference(); + 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 void run(String name, ThrowingRunnable 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 void run(int characteristics, ThrowingRunnable 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 void run(ThrowingRunnable task) throws X { run(null, 0, task); } diff --git a/test/lib/jdk/test/lib/thread/libVThreadPinner.c b/test/lib/jdk/test/lib/thread/libVThreadPinner.c new file mode 100644 index 00000000000..958e636e3db --- /dev/null +++ b/test/lib/jdk/test/lib/thread/libVThreadPinner.c @@ -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 + +#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)(); +}