undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
96
test/jdk/java/lang/Thread/virtual/BigStackChunk.java
Normal file
96
test/jdk/java/lang/Thread/virtual/BigStackChunk.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @bug 8335362
|
||||
* @summary Test virtual thread usage with big stackChunks
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm BigStackChunk
|
||||
*/
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class BigStackChunk {
|
||||
|
||||
void recurse(int cnt, ReentrantLock rlock) {
|
||||
int i1 = cnt;
|
||||
int i2 = i1 + 1;
|
||||
int i3 = i2 + 1;
|
||||
int i4 = i3 + 1;
|
||||
int i5 = i4 + 1;
|
||||
int i6 = i5 + 1;
|
||||
int i7 = i6 + 1;
|
||||
long ll = 2 * (long)i1;
|
||||
float ff = ll + 1.2f;
|
||||
double dd = ff + 1.3D;
|
||||
|
||||
if (cnt > 0) {
|
||||
recurse(cnt - 1, rlock);
|
||||
} else {
|
||||
rlock.lock();
|
||||
rlock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bigStackChunkTest() throws Exception {
|
||||
int VTHREAD_CNT = Runtime.getRuntime().availableProcessors();
|
||||
ReentrantLock rlock = new ReentrantLock();
|
||||
Thread[] vthreads = new Thread[VTHREAD_CNT];
|
||||
|
||||
rlock.lock();
|
||||
for (int i = 0; i < VTHREAD_CNT; i++) {
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
// Set up things so that half of the carriers will commit lots of
|
||||
// pages in the stack while running the mounted vthread and half
|
||||
// will just commit very few ones.
|
||||
if (Math.random() < 0.5) {
|
||||
recurse(300, rlock);
|
||||
} else {
|
||||
recurse(1, rlock);
|
||||
}
|
||||
});
|
||||
}
|
||||
await(vthreads[0], Thread.State.WAITING);
|
||||
// Now we expect that some vthread that recursed a lot is mounted on
|
||||
// a carrier that previously run a vthread that didn't recurse at all.
|
||||
rlock.unlock();
|
||||
|
||||
for (int i = 0; i < VTHREAD_CNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
103
test/jdk/java/lang/Thread/virtual/CancelTimerWithContention.java
Normal file
103
test/jdk/java/lang/Thread/virtual/CancelTimerWithContention.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test cancelling a timeout task for Object.wait(millis) when there is
|
||||
* contention on the timer queue
|
||||
* @requires vm.continuations
|
||||
* @key randomness
|
||||
* @run main/othervm/timeout=480
|
||||
* -Djdk.virtualThreadScheduler.parallelism=2
|
||||
* -Djdk.virtualThreadScheduler.timerQueues=1
|
||||
* CancelTimerWithContention
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CancelTimerWithContention {
|
||||
|
||||
// number of threads
|
||||
private static final int MIN_THREADS = 100;
|
||||
private static final int MAX_THREADS = 10_000;
|
||||
|
||||
// number of monitors to enter
|
||||
private static final int MIN_MONITORS = 2;
|
||||
private static final int MAX_MONITORS = 8;
|
||||
|
||||
private static final Random RAND = new Random();
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int threadCount = MIN_THREADS; threadCount <= MAX_THREADS; threadCount += 100) {
|
||||
System.out.format("%s #threads = %d%n", Instant.now(), threadCount);
|
||||
for (int lockCount = MIN_MONITORS; lockCount <= MAX_MONITORS; lockCount += 2) {
|
||||
test(threadCount, lockCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test threads entering monitors and using Object.wait(millis) to wait. This
|
||||
* testing scenario leads to a mix of contention (with responsible threads using
|
||||
* short timeouts), timed-wait, and cancellation of timer tasks. This scenario
|
||||
* can result in contention of the timer queue.
|
||||
*/
|
||||
static void test(int threadCount, int monitorCount) {
|
||||
var locks = new Object[monitorCount];
|
||||
for (int i = 0; i < monitorCount; i++) {
|
||||
locks[i] = new Object();
|
||||
}
|
||||
|
||||
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
var finished = new AtomicInteger();
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
Object lock = locks[RAND.nextInt(monitorCount)]; // random lock
|
||||
|
||||
executor.submit(() -> {
|
||||
synchronized (lock) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
}
|
||||
finished.incrementAndGet();
|
||||
return null;
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// notify at most one thread until all threads are finished
|
||||
while (finished.get() < threadCount) {
|
||||
for (Object lock : locks) {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
219
test/jdk/java/lang/Thread/virtual/CarrierThreadInfo.java
Normal file
219
test/jdk/java/lang/Thread/virtual/CarrierThreadInfo.java
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test id=default
|
||||
* @bug 8284161 8286788
|
||||
* @summary Test java.lang.management.ThreadInfo contains expected information for carrier threads
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit CarrierThreadInfo
|
||||
*/
|
||||
|
||||
import java.lang.management.LockInfo;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class CarrierThreadInfo {
|
||||
|
||||
/**
|
||||
* Test that ThreadInfo.getLockedMonitors returns information about a lock held by
|
||||
* a carrier thread.
|
||||
*/
|
||||
@Test
|
||||
void testCarrierThreadHoldsLock() throws Exception {
|
||||
Object lock = new Object();
|
||||
ThreadFactory factory = task -> Thread.ofPlatform().unstarted(() -> {
|
||||
synchronized (lock) {
|
||||
task.run();
|
||||
}
|
||||
});
|
||||
|
||||
try (var scheduler = new CustomScheduler(factory)) {
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
Thread vthread = scheduler.forkVirtualThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
awaitTrue(started);
|
||||
|
||||
// carrier threads holds the lock
|
||||
long carrierId = scheduler.carrier().threadId();
|
||||
ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
|
||||
.getThreadInfo(new long[] { carrierId }, true, true)[0];
|
||||
boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
|
||||
.anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
|
||||
assertTrue(holdsLock, "Carrier should hold lock");
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ThreadInfo.getLockedMonitors does not return information about a lock
|
||||
* held by mounted virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testVirtualThreadHoldsLock() throws Exception {
|
||||
ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
try (var scheduler = new CustomScheduler(factory)) {
|
||||
var started = new AtomicBoolean();
|
||||
var lock = new Object();
|
||||
var done = new AtomicBoolean();
|
||||
Thread vthread = scheduler.forkVirtualThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
awaitTrue(started);
|
||||
|
||||
// carrier threads does not hold lock
|
||||
long carrierId = scheduler.carrier().threadId();
|
||||
ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
|
||||
.getThreadInfo(new long[] { carrierId }, true, true)[0];
|
||||
boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
|
||||
.anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
|
||||
assertFalse(holdsLock, "Carrier should not hold lock");
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ThreadInfo.getLockOwnerId and getLockInfo return information about a
|
||||
* synthetic lock that make it appear that the carrier is blocking waiting on the
|
||||
* virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testCarrierThreadWaits() throws Exception {
|
||||
ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
try (var scheduler = new CustomScheduler(factory)) {
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
Thread vthread = scheduler.forkVirtualThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
awaitTrue(started);
|
||||
|
||||
long carrierId = scheduler.carrier().threadId();
|
||||
long vthreadId = vthread.threadId();
|
||||
|
||||
ThreadInfo threadInfo = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId);
|
||||
assertNotNull(threadInfo);
|
||||
|
||||
// carrier should be blocked waiting for lock owned by virtual thread
|
||||
assertEquals(vthreadId, threadInfo.getLockOwnerId());
|
||||
|
||||
// carrier thread should be on blocked waiting on virtual thread
|
||||
LockInfo lockInfo = threadInfo.getLockInfo();
|
||||
assertNotNull(lockInfo);
|
||||
assertEquals(vthread.getClass().getName(), lockInfo.getClassName());
|
||||
assertEquals(System.identityHashCode(vthread), lockInfo.getIdentityHashCode());
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom scheduler with a single carrier thread.
|
||||
*/
|
||||
private static class CustomScheduler implements AutoCloseable {
|
||||
private final ExecutorService pool;
|
||||
private final Executor scheduler;
|
||||
private final AtomicReference<Thread> carrierRef = new AtomicReference<>();
|
||||
|
||||
CustomScheduler(ThreadFactory factory) {
|
||||
pool = Executors.newSingleThreadExecutor(factory);
|
||||
scheduler = task -> {
|
||||
pool.submit(() -> {
|
||||
carrierRef.set(Thread.currentThread());
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
carrierRef.set(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the carrier thread if a virtual thread is mounted.
|
||||
*/
|
||||
Thread carrier() throws InterruptedException {
|
||||
return carrierRef.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a virtual thread to execute the give task.
|
||||
*/
|
||||
Thread forkVirtualThread(Runnable task) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(task);
|
||||
thread.start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
test/jdk/java/lang/Thread/virtual/Collectable.java
Normal file
69
test/jdk/java/lang/Thread/virtual/Collectable.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test that virtual threads are GC'ed
|
||||
* @run junit Collectable
|
||||
*/
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class Collectable {
|
||||
|
||||
/**
|
||||
* Test that an unstarted virtual thread can be GC"ed.
|
||||
*/
|
||||
@Test
|
||||
void testUnstartedThread() {
|
||||
var thread = Thread.ofVirtual().unstarted(() -> { });
|
||||
var ref = new WeakReference<>(thread);
|
||||
thread = null;
|
||||
waitUntilCleared(ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a terminated virtual thread can be GC'ed.
|
||||
*/
|
||||
@Test
|
||||
void testTerminatedThread() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> { });
|
||||
thread.join();
|
||||
var ref = new WeakReference<>(thread);
|
||||
thread = null;
|
||||
waitUntilCleared(ref);
|
||||
}
|
||||
|
||||
private static void waitUntilCleared(WeakReference<?> ref) {
|
||||
while (ref.get() != null) {
|
||||
System.gc();
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignore) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
286
test/jdk/java/lang/Thread/virtual/CustomScheduler.java
Normal file
286
test/jdk/java/lang/Thread/virtual/CustomScheduler.java
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test virtual threads using a custom scheduler
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit CustomScheduler
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class CustomScheduler {
|
||||
private static ExecutorService scheduler1;
|
||||
private static ExecutorService scheduler2;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
scheduler1 = Executors.newFixedThreadPool(1);
|
||||
scheduler2 = Executors.newFixedThreadPool(1);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void shutdown() {
|
||||
scheduler1.shutdown();
|
||||
scheduler2.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test platform thread creating a virtual thread that uses a custom scheduler.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler1() throws Exception {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
Thread thread = factory.newThread(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread creating a virtual thread that uses a custom scheduler.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler2() throws Exception {
|
||||
VThreadRunner.run(this::testCustomScheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread using custom scheduler creating a virtual thread.
|
||||
* The scheduler should be inherited.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler3() throws Exception {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
Thread thread = factory.newThread(() -> {
|
||||
try {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
}).join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread using custom scheduler creating a virtual thread
|
||||
* that uses a different custom scheduler.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler4() throws Exception {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory1 = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
ThreadFactory factory2 = VThreadScheduler.virtualThreadFactory(scheduler2);
|
||||
Thread thread1 = factory1.newThread(() -> {
|
||||
try {
|
||||
Thread thread2 = factory2.newThread(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
});
|
||||
thread2.start();
|
||||
thread2.join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread1.start();
|
||||
thread1.join();
|
||||
assertTrue(ref.get() == scheduler2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test running task on a virtual thread, should thrown WrongThreadException.
|
||||
*/
|
||||
@Test
|
||||
void testBadCarrier() {
|
||||
Executor scheduler = (task) -> {
|
||||
var exc = new AtomicReference<Throwable>();
|
||||
try {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
task.run();
|
||||
fail();
|
||||
} catch (Throwable e) {
|
||||
exc.set(e);
|
||||
}
|
||||
}).join();
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
assertTrue(exc.get() instanceof WrongThreadException);
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test parking with the virtual thread interrupt set, should not leak to the
|
||||
* carrier thread when the task completes.
|
||||
*/
|
||||
@Test
|
||||
void testParkWithInterruptSet() {
|
||||
Thread carrier = Thread.currentThread();
|
||||
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
|
||||
try {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
Thread.yield();
|
||||
});
|
||||
vthread.start();
|
||||
assertTrue(vthread.isInterrupted());
|
||||
assertFalse(carrier.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test terminating with the virtual thread interrupt set, should not leak to
|
||||
* the carrier thread when the task completes.
|
||||
*/
|
||||
@Test
|
||||
void testTerminateWithInterruptSet() {
|
||||
Thread carrier = Thread.currentThread();
|
||||
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
|
||||
try {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
});
|
||||
vthread.start();
|
||||
assertTrue(vthread.isInterrupted());
|
||||
assertFalse(carrier.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test running task with the carrier's interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testRunWithInterruptSet() throws Exception {
|
||||
assumeFalse(Thread.currentThread().isVirtual(), "Main thread is a virtual thread");
|
||||
Executor scheduler = (task) -> {
|
||||
Thread.currentThread().interrupt();
|
||||
task.run();
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
try {
|
||||
AtomicBoolean interrupted = new AtomicBoolean();
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
interrupted.set(Thread.currentThread().isInterrupted());
|
||||
});
|
||||
vthread.start();
|
||||
assertFalse(vthread.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom scheduler throwing OOME when starting a thread.
|
||||
*/
|
||||
@Test
|
||||
void testThreadStartOOME() throws Exception {
|
||||
Executor scheduler = task -> {
|
||||
System.err.println("OutOfMemoryError");
|
||||
throw new OutOfMemoryError();
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(() -> { });
|
||||
assertThrows(OutOfMemoryError.class, thread::start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom scheduler throwing OOME when unparking a thread.
|
||||
*/
|
||||
@Test
|
||||
void testThreadUnparkOOME() throws Exception {
|
||||
try (ExecutorService executor = Executors.newFixedThreadPool(1)) {
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Executor scheduler = task -> {
|
||||
switch (counter.getAndIncrement()) {
|
||||
case 0 -> executor.execute(task); // Thread.start
|
||||
case 1, 2 -> { // unpark attempt 1+2
|
||||
System.err.println("OutOfMemoryError");
|
||||
throw new OutOfMemoryError();
|
||||
}
|
||||
default -> executor.execute(task);
|
||||
}
|
||||
executor.execute(task);
|
||||
};
|
||||
|
||||
// start thread and wait for it to park
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
var thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// unpark thread, this should retry until OOME is not thrown
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
476
test/jdk/java/lang/Thread/virtual/JfrEvents.java
Normal file
476
test/jdk/java/lang/Thread/virtual/JfrEvents.java
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Basic test for JFR jdk.VirtualThreadXXX events
|
||||
* @requires vm.continuations & vm.hasJFR
|
||||
* @modules jdk.jfr java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native --enable-native-access=ALL-UNNAMED JfrEvents
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
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.locks.LockSupport;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
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; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class JfrEvents {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need at least two carriers to test pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
|
||||
*/
|
||||
@Test
|
||||
void testVirtualThreadStartAndEnd() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadStart");
|
||||
recording.enable("jdk.VirtualThreadEnd");
|
||||
|
||||
// execute 100 tasks, each in their own virtual thread
|
||||
recording.start();
|
||||
try {
|
||||
List<Thread> threads = IntStream.range(0, 100)
|
||||
.mapToObj(_ -> Thread.startVirtualThread(() -> { }))
|
||||
.toList();
|
||||
for (Thread t : threads) {
|
||||
t.join();
|
||||
}
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
Map<String, Integer> events = sumEvents(recording);
|
||||
System.err.println(events);
|
||||
|
||||
int startCount = events.getOrDefault("jdk.VirtualThreadStart", 0);
|
||||
int endCount = events.getOrDefault("jdk.VirtualThreadEnd", 0);
|
||||
assertEquals(100, startCount);
|
||||
assertEquals(100, endCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when parking while pinned.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testParkWhenPinned(boolean timed) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and park
|
||||
awaitTrue(started);
|
||||
await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when blocking on monitor while pinned.
|
||||
*/
|
||||
@Test
|
||||
void testBlockWhenPinned() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Object lock = new Object();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
synchronized (lock) { }
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
awaitTrue(started);
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when waiting with Object.wait while pinned.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitWhenPinned(boolean timed) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Object lock = new Object();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (timed) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(started);
|
||||
await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
} finally {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when parking in a class initializer.
|
||||
*/
|
||||
@Test
|
||||
void testParkInClassInitializer() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
LockSupport.park();
|
||||
}
|
||||
static void m() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
Thread vthread = Thread.startVirtualThread(() -> {
|
||||
started.set(true);
|
||||
TestClass.m();
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for it to start and park
|
||||
awaitTrue(started);
|
||||
await(vthread, Thread.State.WAITING);
|
||||
} finally {
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when blocking on monitor in a class initializer.
|
||||
*/
|
||||
@Test
|
||||
void testBlockInClassInitializer() throws Exception {
|
||||
class LockHolder {
|
||||
static final Object lock = new Object();
|
||||
}
|
||||
class TestClass {
|
||||
static {
|
||||
synchronized (LockHolder.lock) { }
|
||||
}
|
||||
static void m() {
|
||||
// no nothing
|
||||
}
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
Thread vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.set(true);
|
||||
TestClass.m();
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized (LockHolder.lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
awaitTrue(started);
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when waiting for a class initializer while pinned.
|
||||
*/
|
||||
@Test
|
||||
void testWaitingForClassInitializerWhenPinned() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
LockSupport.park();
|
||||
}
|
||||
static void m() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started1 = new AtomicBoolean();
|
||||
var started2 = new AtomicBoolean();
|
||||
|
||||
Thread vthread1 = Thread.ofVirtual().unstarted(() -> {
|
||||
started1.set(true);
|
||||
TestClass.m();
|
||||
});
|
||||
Thread vthread2 = Thread.ofVirtual().unstarted(() -> {
|
||||
started2.set(true);
|
||||
VThreadPinner.runPinned(() -> {
|
||||
TestClass.m();
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// start first virtual thread and wait for it to start + park
|
||||
vthread1.start();
|
||||
awaitTrue(started1);
|
||||
await(vthread1, Thread.State.WAITING);
|
||||
|
||||
// start second virtual thread and wait for it to start
|
||||
vthread2.start();
|
||||
awaitTrue(started2);
|
||||
|
||||
// give time for second virtual thread to wait in VM
|
||||
Thread.sleep(3000);
|
||||
|
||||
} finally {
|
||||
LockSupport.unpark(vthread1);
|
||||
vthread1.join();
|
||||
vthread2.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// the recording should have a pinned event for vthread2
|
||||
assertContainsPinnedEvent(recording, vthread2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadSubmitFailed event.
|
||||
*/
|
||||
@Test
|
||||
void testVirtualThreadSubmitFailed() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadSubmitFailed");
|
||||
|
||||
recording.start();
|
||||
try (ExecutorService pool = Executors.newCachedThreadPool()) {
|
||||
Executor scheduler = task -> pool.execute(task);
|
||||
|
||||
// create virtual thread that uses custom scheduler
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
// start a thread
|
||||
Thread thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
|
||||
// wait for thread to park
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// shutdown scheduler
|
||||
pool.shutdown();
|
||||
|
||||
// unpark, the submit should fail
|
||||
try {
|
||||
LockSupport.unpark(thread);
|
||||
fail();
|
||||
} catch (RejectedExecutionException expected) { }
|
||||
|
||||
// start another thread, it should fail and an event should be recorded
|
||||
try {
|
||||
factory.newThread(LockSupport::park).start();
|
||||
throw new RuntimeException("RejectedExecutionException expected");
|
||||
} catch (RejectedExecutionException expected) { }
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
|
||||
System.err.println(submitFailedEvents);
|
||||
assertTrue(submitFailedEvents.size() == 2, "Expected two events");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events in the given recording with the given name.
|
||||
*/
|
||||
private static List<RecordedEvent> find(Recording recording, String name) throws IOException {
|
||||
Path recordingFile = recordingFile(recording);
|
||||
return RecordingFile.readAllEvents(recordingFile)
|
||||
.stream()
|
||||
.filter(e -> e.getEventType().getName().equals(name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the events from the recording and return a map of event name to count.
|
||||
*/
|
||||
private static Map<String, Integer> sumEvents(Recording recording) throws IOException {
|
||||
Path recordingFile = recordingFile(recording);
|
||||
List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile);
|
||||
return events.stream()
|
||||
.map(RecordedEvent::getEventType)
|
||||
.collect(Collectors.groupingBy(EventType::getName,
|
||||
Collectors.summingInt(x -> 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to the recording file.
|
||||
*/
|
||||
private static Path recordingFile(Recording recording) throws IOException {
|
||||
Path recordingFile = recording.getDestination();
|
||||
if (recordingFile == null) {
|
||||
ProcessHandle h = ProcessHandle.current();
|
||||
recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
|
||||
recording.dump(recordingFile);
|
||||
}
|
||||
return recordingFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread.
|
||||
*/
|
||||
private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException {
|
||||
List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned");
|
||||
assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording");
|
||||
System.err.println(pinnedEvents);
|
||||
|
||||
long tid = thread.threadId();
|
||||
assertTrue(pinnedEvents.stream()
|
||||
.anyMatch(e -> e.getThread().getJavaThreadId() == tid),
|
||||
"jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given boolean to be set to true.
|
||||
*/
|
||||
private void awaitTrue(AtomicBoolean b) throws InterruptedException {
|
||||
while (!b.get()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
490
test/jdk/java/lang/Thread/virtual/KlassInit.java
Normal file
490
test/jdk/java/lang/Thread/virtual/KlassInit.java
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xint -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=gc
|
||||
* @library /test/lib
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @run junit/othervm/timeout=480 -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000
|
||||
* -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run
|
||||
* -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class KlassInit {
|
||||
private static final int MAX_VTHREAD_COUNT = 8 * Runtime.getRuntime().availableProcessors();
|
||||
|
||||
private static final CountDownLatch finishInvokeStatic1 = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishInvokeStatic2 = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishInvokeStatic3 = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishNew = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishGetStatic = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishPutStatic = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishFailedInit = new CountDownLatch(1);
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on invokestatic bytecode release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitInvokeStatic1() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishInvokeStatic1.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass.m();
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishInvokeStatic1.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with static method that takes arguments.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitInvokeStatic2() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishInvokeStatic2.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m(ArrayList<String> list, int id) {
|
||||
String str = list.get(0);
|
||||
if (str != null && str.equals("VThread#" + id)) {
|
||||
list.add("Success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
ArrayList<String>[] lists = new ArrayList[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
lists[i] = new ArrayList<>(List.of("VThread#" + i));
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass.m(lists[id], id);
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
finishInvokeStatic2.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
assertEquals(lists[i].get(1), "Success");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invokestatic as first bytecode in method.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitInvokeStatic3() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishInvokeStatic3.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {
|
||||
}
|
||||
}
|
||||
class Driver {
|
||||
static void foo() {
|
||||
TestClass.m();
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
started[0] = new CountDownLatch(1);
|
||||
vthreads[0] = Thread.ofVirtual().start(() -> {
|
||||
started[0].countDown();
|
||||
TestClass.m();
|
||||
});
|
||||
started[0].await();
|
||||
await(vthreads[0], Thread.State.WAITING);
|
||||
|
||||
for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
Driver.foo();
|
||||
});
|
||||
}
|
||||
for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishInvokeStatic3.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on new bytecode release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitNew() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishNew.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
void m() {
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass x = new TestClass();
|
||||
x.m();
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishNew.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on getstatic bytecode release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitGetStatic() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishGetStatic.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
public static int NUMBER = 150;
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
AtomicInteger[] result = new AtomicInteger[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
result[i] = new AtomicInteger();
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
result[id].set(TestClass.NUMBER);
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishGetStatic.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
assertEquals(result[i].get(), TestClass.NUMBER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on putstatic release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitPutStatic() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishPutStatic.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
public static int NUMBER;
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass.NUMBER = id;
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishPutStatic.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that interruptions during preemption on klass init
|
||||
* are preserved.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("interruptTestCases")
|
||||
void testReleaseAtKlassInitPreserverInterrupt(int timeout, Runnable m, CountDownLatch finish) throws Exception {
|
||||
// Start vthread1 and wait until it blocks in TestClassX initializer
|
||||
var vthread1_started = new CountDownLatch(1);
|
||||
var vthread1 = Thread.ofVirtual().start(() -> {
|
||||
vthread1_started.countDown();
|
||||
m.run();
|
||||
});
|
||||
vthread1_started.await();
|
||||
await(vthread1, Thread.State.WAITING);
|
||||
|
||||
// Start vthread2 and wait until it gets preempted on TestClassX initialization
|
||||
var lock = new Object();
|
||||
var interruptedException = new AtomicBoolean();
|
||||
var vthread2_started = new CountDownLatch(1);
|
||||
var vthread2 = Thread.ofVirtual().start(() -> {
|
||||
vthread2_started.countDown();
|
||||
m.run();
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// check stack trace has the expected frames
|
||||
Set<String> expected = Set.of("wait0", "wait", "run");
|
||||
Set<String> methods = Stream.of(e.getStackTrace())
|
||||
.map(StackTraceElement::getMethodName)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(methods.containsAll(expected));
|
||||
interruptedException.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
vthread2_started.await();
|
||||
await(vthread2, Thread.State.WAITING);
|
||||
|
||||
// Interrupt vthread2 and let initialization of TestClassX finish
|
||||
vthread2.interrupt();
|
||||
finish.countDown();
|
||||
vthread1.join();
|
||||
vthread2.join();
|
||||
assertTrue(interruptedException.get());
|
||||
}
|
||||
|
||||
static CountDownLatch finishInterrupt0 = new CountDownLatch(1);
|
||||
class TestClass0 {
|
||||
static {
|
||||
try {
|
||||
finishInterrupt0.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {}
|
||||
}
|
||||
|
||||
static CountDownLatch finishInterrupt30000 = new CountDownLatch(1);
|
||||
class TestClass30000 {
|
||||
static {
|
||||
try {
|
||||
finishInterrupt30000.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {}
|
||||
}
|
||||
|
||||
static CountDownLatch finishInterruptMax = new CountDownLatch(1);
|
||||
class TestClassMax {
|
||||
static {
|
||||
try {
|
||||
finishInterruptMax.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {}
|
||||
}
|
||||
|
||||
static Stream<Arguments> interruptTestCases() {
|
||||
return Stream.of(
|
||||
Arguments.of(0, (Runnable) TestClass0::m, finishInterrupt0),
|
||||
Arguments.of(30000, (Runnable) TestClass30000::m, finishInterrupt30000),
|
||||
Arguments.of(Integer.MAX_VALUE, (Runnable) TestClassMax::m, finishInterruptMax)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case of threads blocked waiting for klass to be initialized
|
||||
* when the klass initialization fails.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitFailedInit() throws Exception {
|
||||
class TestClass {
|
||||
static int[] a = {1, 2, 3};
|
||||
static {
|
||||
try {
|
||||
finishFailedInit.await();
|
||||
a[3] = 4;
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
AtomicInteger failedCount = new AtomicInteger();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
try {
|
||||
TestClass.m();
|
||||
} catch (NoClassDefFoundError e) {
|
||||
failedCount.getAndIncrement();
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
failedCount.getAndIncrement();
|
||||
}
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishFailedInit.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
assertEquals(MAX_VTHREAD_COUNT, failedCount.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
217
test/jdk/java/lang/Thread/virtual/Locking.java
Normal file
217
test/jdk/java/lang/Thread/virtual/Locking.java
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test virtual threads using java.util.concurrent locks
|
||||
* @library /test/lib
|
||||
* @run junit Locking
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class Locking {
|
||||
|
||||
/**
|
||||
* Test lock/unlock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
lock.lock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
lock.unlock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test tryLock/unlock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
boolean acquired = lock.tryLock();
|
||||
assertTrue(acquired);
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
lock.unlock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lock/lock/unlock/unlock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 0);
|
||||
lock.lock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 1);
|
||||
lock.lock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 2);
|
||||
lock.unlock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 1);
|
||||
lock.unlock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked by platform thread, virtual thread tries to lock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock4() throws Exception {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
var holdsLock = new AtomicBoolean();
|
||||
|
||||
Thread thread;
|
||||
|
||||
// thread acquires lock
|
||||
lock.lock();
|
||||
try {
|
||||
thread = Thread.ofVirtual().start(() -> {
|
||||
lock.lock(); // should block
|
||||
holdsLock.set(true);
|
||||
LockSupport.park();
|
||||
lock.unlock();
|
||||
holdsLock.set(false);
|
||||
});
|
||||
// give time for virtual thread to block
|
||||
Thread.sleep(500);
|
||||
assertFalse(holdsLock.get());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
// virtual thread should acquire lock, park, unpark, and then release lock
|
||||
while (!holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
LockSupport.unpark(thread);
|
||||
while (holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked by virtual thread, platform thread tries to lock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock5() throws Exception {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
LockSupport.park();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
// wat for virtual thread to acquire lock
|
||||
while (!lock.isLocked()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
// thread cannot acquire lock
|
||||
try {
|
||||
assertFalse(lock.tryLock());
|
||||
} finally {
|
||||
// virtual thread should unlock
|
||||
LockSupport.unpark(thread);
|
||||
|
||||
// thread should be able to acquire lock
|
||||
lock.lock();
|
||||
lock.unlock();
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked by virtual thread, another virtual thread tries to lock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock6() throws Exception {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
LockSupport.park();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
// wat for virtual thread to acquire lock
|
||||
while (!lock.isLocked()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
|
||||
var holdsLock = new AtomicBoolean();
|
||||
var thread2 = Thread.ofVirtual().start(() -> {
|
||||
lock.lock();
|
||||
holdsLock.set(true);
|
||||
LockSupport.park();
|
||||
lock.unlock();
|
||||
holdsLock.set(false);
|
||||
});
|
||||
|
||||
// virtual thread2 should block
|
||||
Thread.sleep(1000);
|
||||
assertFalse(holdsLock.get());
|
||||
|
||||
// unpark virtual thread1
|
||||
LockSupport.unpark(thread1);
|
||||
|
||||
// virtual thread2 should acquire lock
|
||||
while (!holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
// unpark virtual thread and it should release lock
|
||||
LockSupport.unpark(thread2);
|
||||
while (holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
}
|
||||
509
test/jdk/java/lang/Thread/virtual/MiscMonitorTests.java
Normal file
509
test/jdk/java/lang/Thread/virtual/MiscMonitorTests.java
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Tests for object monitors that have been useful to find bugs
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xint MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xcomp MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel3
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:TieredStopAtLevel=3 MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @summary Test virtual threads using synchronized
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=gc
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @library /test/lib
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 MiscMonitorTests
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MiscMonitorTests {
|
||||
static final int CARRIER_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* Test that yielding while holding monitors releases carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseOnYield() throws Exception {
|
||||
try (var test = new TestReleaseOnYield()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestReleaseOnYield extends TestBase {
|
||||
final Object lock = new Object();
|
||||
volatile boolean finish;
|
||||
volatile int counter;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT;
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "Batch1");
|
||||
sleep(500); // Give time for threads to reach Thread.yield
|
||||
startVThreads(() -> bar(), vthreadCount, "Batch2");
|
||||
|
||||
while (counter != vthreadCount) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
while (!finish) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
void bar() {
|
||||
synchronized (lock) {
|
||||
counter++;
|
||||
}
|
||||
System.err.println("Exiting bar from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test yielding while holding monitors with recursive locking releases carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseOnYieldRecursive() throws Exception {
|
||||
try (var test = new TestReleaseOnYieldRecursive()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestReleaseOnYieldRecursive extends TestBase {
|
||||
final Object lock = new Object();
|
||||
volatile boolean finish;
|
||||
volatile int counter;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT;
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "Batch1");
|
||||
sleep(500); // Give time for threads to reach Thread.yield
|
||||
startVThreads(() -> bar(), vthreadCount, "Batch2");
|
||||
|
||||
while (counter != 2 * vthreadCount) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
while (!finish) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
void bar() {
|
||||
synchronized (lock) {
|
||||
counter++;
|
||||
}
|
||||
recursive(10);
|
||||
System.err.println("Exiting bar from thread " + Thread.currentThread().getName());
|
||||
};
|
||||
|
||||
void recursive(int count) {
|
||||
synchronized (Thread.currentThread()) {
|
||||
if (count > 0) {
|
||||
recursive(count - 1);
|
||||
} else {
|
||||
synchronized (lock) {
|
||||
counter++;
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that contention on monitorenter releases carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseOnContention() throws Exception {
|
||||
try (var test = new TestReleaseOnContention()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestReleaseOnContention extends TestBase {
|
||||
final Object lock = new Object();
|
||||
volatile boolean finish;
|
||||
volatile int counter;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
sleep(500); // Give time for threads to reach synchronized (lock)
|
||||
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
synchronized (lock) {
|
||||
while (!finish) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contention on monitorenter with extra monitors on stack shared by all threads.
|
||||
*/
|
||||
@Test
|
||||
void testContentionMultipleMonitors() throws Exception {
|
||||
try (var test = new TestContentionMultipleMonitors()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestContentionMultipleMonitors extends TestBase {
|
||||
static int MONITOR_COUNT = 12;
|
||||
final Object[] lockArray = new Object[MONITOR_COUNT];
|
||||
final AtomicInteger workerCount = new AtomicInteger(0);
|
||||
volatile boolean finish;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
|
||||
sleep(5000);
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
assertEquals(vthreadCount, workerCount.get());
|
||||
}
|
||||
|
||||
void foo() {
|
||||
while (!finish) {
|
||||
int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
recursive1(lockNumber, lockNumber);
|
||||
}
|
||||
}
|
||||
workerCount.getAndIncrement();
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
public void recursive1(int depth, int lockNumber) {
|
||||
if (depth > 0) {
|
||||
recursive1(depth - 1, lockNumber);
|
||||
} else {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
}
|
||||
recursive2(lockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public void recursive2(int lockNumber) {
|
||||
if (lockNumber + 2 <= MONITOR_COUNT - 1) {
|
||||
lockNumber += 2;
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
Thread.yield();
|
||||
recursive2(lockNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contention on monitorenter with extra monitors on stack both local only and shared by all threads.
|
||||
*/
|
||||
@Test
|
||||
void testContentionMultipleMonitors2() throws Exception {
|
||||
try (var test = new TestContentionMultipleMonitors2()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestContentionMultipleMonitors2 extends TestBase {
|
||||
int MONITOR_COUNT = 12;
|
||||
final Object[] lockArray = new Object[MONITOR_COUNT];
|
||||
final AtomicInteger workerCount = new AtomicInteger(0);
|
||||
volatile boolean finish;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
|
||||
sleep(5000);
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
assertEquals(vthreadCount, workerCount.get());
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object[] myLockArray = new Object[MONITOR_COUNT];
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
myLockArray[i] = new Object();
|
||||
}
|
||||
|
||||
while (!finish) {
|
||||
int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
|
||||
synchronized (myLockArray[lockNumber]) {
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
recursive1(lockNumber, lockNumber, myLockArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
workerCount.getAndIncrement();
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
public void recursive1(int depth, int lockNumber, Object[] myLockArray) {
|
||||
if (depth > 0) {
|
||||
recursive1(depth - 1, lockNumber, myLockArray);
|
||||
} else {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
}
|
||||
recursive2(lockNumber, myLockArray);
|
||||
}
|
||||
}
|
||||
|
||||
public void recursive2(int lockNumber, Object[] myLockArray) {
|
||||
if (lockNumber + 2 <= MONITOR_COUNT - 1) {
|
||||
lockNumber += 2;
|
||||
synchronized (myLockArray[lockNumber]) {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
}
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
Thread.yield();
|
||||
recursive2(lockNumber, myLockArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test contention on monitorenter with synchronized methods.
|
||||
*/
|
||||
@Test
|
||||
void testContentionWithSyncMethods() throws Exception {
|
||||
try (var test = new TestContentionWithSyncMethods()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestContentionWithSyncMethods extends TestBase {
|
||||
static final int MONITOR_COUNT = 12;
|
||||
final Object[] lockArray = new Object[MONITOR_COUNT];
|
||||
final AtomicInteger workerCount = new AtomicInteger(0);
|
||||
volatile boolean finish;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
|
||||
sleep(5000);
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
assertEquals(vthreadCount, workerCount.get());
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object myLock = new Object();
|
||||
|
||||
while (!finish) {
|
||||
int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
|
||||
synchronized (myLock) {
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
recursive(lockNumber, myLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
workerCount.getAndIncrement();
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
};
|
||||
|
||||
synchronized void recursive(int depth, Object myLock) {
|
||||
if (depth > 0) {
|
||||
recursive(depth - 1, myLock);
|
||||
} else {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
} else {
|
||||
synchronized (myLock) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test wait/notify mechanism.
|
||||
*/
|
||||
@Test
|
||||
void waitNotifyTest() throws Exception {
|
||||
int threadCount = 1000;
|
||||
int waitTime = 50;
|
||||
Thread[] vthread = new Thread[threadCount];
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (System.currentTimeMillis() - start < 5000) {
|
||||
CountDownLatch latchStart = new CountDownLatch(threadCount);
|
||||
CountDownLatch latchFinish = new CountDownLatch(threadCount);
|
||||
Object object = new Object();
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
vthread[i] = Thread.ofVirtual().start(() -> {
|
||||
synchronized (object) {
|
||||
try {
|
||||
latchStart.countDown();
|
||||
object.wait(waitTime);
|
||||
} catch (InterruptedException e) {
|
||||
//do nothing;
|
||||
}
|
||||
}
|
||||
latchFinish.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
latchStart.await();
|
||||
synchronized (object) {
|
||||
object.notifyAll();
|
||||
}
|
||||
latchFinish.await();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
vthread[i].join();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
//do nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class TestBase implements AutoCloseable {
|
||||
final ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT);
|
||||
final List<Thread[]> vthreadList = new ArrayList<>();
|
||||
|
||||
abstract void runTest() throws Exception;
|
||||
|
||||
void startVThreads(Runnable r, int count, String name) {
|
||||
Thread vthreads[] = new Thread[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
vthreads[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name(name + "-" + i).start(r);
|
||||
}
|
||||
vthreadList.add(vthreads);
|
||||
}
|
||||
|
||||
void joinVThreads() throws Exception {
|
||||
for (Thread[] vthreads : vthreadList) {
|
||||
for (Thread vthread : vthreads) {
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sleep(long ms) throws Exception {
|
||||
Thread.sleep(ms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
scheduler.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
547
test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
Normal file
547
test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual thread with monitor enter/exit
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xint --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:-TieredCompilation --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class MonitorEnterExit {
|
||||
static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
|
||||
static final int MAX_ENTER_DEPTH = 256;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for tests that pin
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with no contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnterNoContention() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with contention, monitor is held by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWhenHeldByPlatformThread() throws Exception {
|
||||
testEnterWithContention();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with contention, monitor is held by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWhenHeldByVirtualThread() throws Exception {
|
||||
VThreadRunner.run(this::testEnterWithContention);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with contention, monitor will be held by caller thread.
|
||||
*/
|
||||
private void testEnterWithContention() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor reenter.
|
||||
*/
|
||||
@Test
|
||||
void testReenter() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
testReenter(lock, 0);
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
private void testReenter(Object lock, int depth) {
|
||||
if (depth < MAX_ENTER_DEPTH) {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
testReenter(lock, depth + 1);
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor reenter when there are other threads blocked trying to enter.
|
||||
*/
|
||||
@Test
|
||||
void testReenterWithContention() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
testReenter(lock, 0, threads);
|
||||
|
||||
// wait for threads to terminate
|
||||
for (Thread vthread : threads) {
|
||||
vthread.join();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
|
||||
if (depth < MAX_ENTER_DEPTH) {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
|
||||
// start platform or virtual thread that blocks waiting to enter
|
||||
var started = new CountDownLatch(1);
|
||||
ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
|
||||
? Thread.ofPlatform().factory()
|
||||
: Thread.ofVirtual().factory();
|
||||
var thread = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
/* do nothing */
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
threads.add(thread);
|
||||
|
||||
// test reenter
|
||||
testReenter(lock, depth + 1, threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter when pinned.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWhenPinned() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor reenter when pinned.
|
||||
*/
|
||||
@Test
|
||||
void testReenterWhenPinned() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var lock = new Object();
|
||||
synchronized (lock) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contended monitor enter when pinned. Monitor is held by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
|
||||
testEnterWithContentionWhenPinned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contended monitor enter when pinned. Monitor is held by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
|
||||
VThreadRunner.run(this::testEnterWithContentionWhenPinned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contended monitor enter when pinned, monitor will be held by caller thread.
|
||||
*/
|
||||
private void testEnterWithContentionWhenPinned() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
Thread vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
entered.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
synchronized (lock) {
|
||||
// start thread and wait for it to block
|
||||
vthread.start();
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
vthread.join();
|
||||
|
||||
// check thread entered monitor
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that blocking waiting to enter a monitor releases the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseWhenBlocked() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = new Object();
|
||||
|
||||
// thread enters monitor
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized (lock) {
|
||||
// start thread and wait for it to block
|
||||
vthread1.start();
|
||||
started.await();
|
||||
await(vthread1, Thread.State.BLOCKED);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
}
|
||||
} finally {
|
||||
vthread1.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lots of virtual threads blocked waiting to enter a monitor. If the number
|
||||
* of virtual threads exceeds the number of carrier threads this test will hang if
|
||||
* carriers aren't released.
|
||||
*/
|
||||
@Test
|
||||
void testManyBlockedThreads() throws Exception {
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
var lock = new Object();
|
||||
synchronized (lock) {
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
}
|
||||
});
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
vthreads[i] = vthread;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of elements that are ordered pairs of platform and virtual thread
|
||||
* counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
|
||||
*/
|
||||
static Stream<Arguments> threadCounts() {
|
||||
return IntStream.range(0, 17)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(i -> i)
|
||||
.flatMap(np -> IntStream.range(2, 33)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(vp -> Arguments.of(np, vp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test mutual exclusion of monitors with platform and virtual threads.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
class Counter {
|
||||
int count;
|
||||
synchronized void increment() {
|
||||
count++;
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
var counter = new Counter();
|
||||
int nThreads = nPlatformThreads + nVirtualThreads;
|
||||
var threads = new Thread[nThreads];
|
||||
int index = 0;
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads[index] = Thread.ofPlatform()
|
||||
.name("platform-" + index)
|
||||
.unstarted(counter::increment);
|
||||
index++;
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads[index] = Thread.ofVirtual()
|
||||
.name("virtual-" + index)
|
||||
.unstarted(counter::increment);
|
||||
index++;
|
||||
}
|
||||
// start all threads
|
||||
for (Thread thread : threads) {
|
||||
thread.start();
|
||||
}
|
||||
// wait for all threads to terminate
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
assertEquals(nThreads, counter.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unblocking a virtual thread waiting to enter a monitor held by a platform thread.
|
||||
*/
|
||||
@RepeatedTest(20)
|
||||
void testUnblockingByPlatformThread() throws Exception {
|
||||
testUnblocking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unblocking a virtual thread waiting to enter a monitor held by another
|
||||
* virtual thread.
|
||||
*/
|
||||
@RepeatedTest(20)
|
||||
void testUnblockingByVirtualThread() throws Exception {
|
||||
VThreadRunner.run(this::testUnblocking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unblocking a virtual thread waiting to enter a monitor, monitor will be
|
||||
* initially be held by caller thread.
|
||||
*/
|
||||
private void testUnblocking() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
entered.set(true);
|
||||
}
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
started.await();
|
||||
|
||||
// random delay before exiting monitor
|
||||
switch (ThreadLocalRandom.current().nextInt(4)) {
|
||||
case 0 -> { /* no delay */}
|
||||
case 1 -> Thread.onSpinWait();
|
||||
case 2 -> Thread.yield();
|
||||
case 3 -> await(vthread, Thread.State.BLOCKED);
|
||||
default -> fail();
|
||||
}
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that unblocking a virtual thread waiting to enter a monitor does not consume
|
||||
* the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotConsumed() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
synchronized (lock) { } // should block
|
||||
LockSupport.park(); // should not park
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
vthread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that unblocking a virtual thread waiting to enter a monitor does not make
|
||||
* available the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotOffered() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) { } // should block
|
||||
LockSupport.park(); // should park
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
|
||||
try {
|
||||
// wait for thread to park, it should not terminate
|
||||
await(vthread, Thread.State.WAITING);
|
||||
vthread.join(Duration.ofMillis(100));
|
||||
assertEquals(Thread.State.WAITING, vthread.getState());
|
||||
} finally {
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
148
test/jdk/java/lang/Thread/virtual/MonitorEnterWaitOOME.java
Normal file
148
test/jdk/java/lang/Thread/virtual/MonitorEnterWaitOOME.java
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=monitorenter
|
||||
* @bug 8345266
|
||||
* @summary Test OOM while trying to unmount vthread on monitorenter
|
||||
* @requires vm.continuations & vm.gc.G1 & vm.opt.DisableExplicitGC != "true"
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseG1GC -Xmx48M MonitorEnterWaitOOME false
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timedwait
|
||||
* @summary Test OOM while trying to unmount vthread on Object.wait
|
||||
* @requires vm.continuations & vm.gc.G1 & vm.opt.DisableExplicitGC != "true"
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseG1GC -Xmx48M MonitorEnterWaitOOME true 5
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=untimedwait
|
||||
* @summary Test OOM while trying to unmount vthread on Object.wait
|
||||
* @requires vm.continuations & vm.gc.G1 & vm.opt.DisableExplicitGC != "true"
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseG1GC -Xmx48M MonitorEnterWaitOOME true 0
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class MonitorEnterWaitOOME {
|
||||
static volatile Object data;
|
||||
static Thread.State dummyState = Thread.State.RUNNABLE; // load java.lang.Thread$State
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
final boolean testWait = args.length >= 1 ? Boolean.parseBoolean(args[0]) : false;
|
||||
final long timeout = testWait && args.length == 2 ? Long.parseLong(args[1]) : 0L;
|
||||
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
|
||||
Thread vthread;
|
||||
var lock = new Object();
|
||||
var canFillHeap = new AtomicBoolean();
|
||||
var heapFilled = new AtomicBoolean();
|
||||
var heapCollected = new AtomicBoolean();
|
||||
var exRef = new AtomicReference<Throwable>();
|
||||
synchronized (lock) {
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
awaitTrue(canFillHeap);
|
||||
data = fillHeap();
|
||||
heapFilled.set(true);
|
||||
synchronized (lock) {
|
||||
if (testWait) {
|
||||
lock.wait(timeout);
|
||||
}
|
||||
}
|
||||
data = null;
|
||||
System.gc();
|
||||
heapCollected.set(true);
|
||||
} catch (Throwable e) {
|
||||
data = null;
|
||||
System.gc(); // avoid nested OOME
|
||||
exRef.set(e);
|
||||
}
|
||||
});
|
||||
canFillHeap.set(true);
|
||||
awaitTrue(heapFilled);
|
||||
awaitState(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
if (testWait && timeout == 0) {
|
||||
awaitState(vthread, Thread.State.WAITING);
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
joinVThread(vthread, heapCollected, exRef);
|
||||
assert exRef.get() == null;
|
||||
}
|
||||
|
||||
private static Object[] fillHeap() {
|
||||
Object[] first = null, last = null;
|
||||
int size = 1 << 20;
|
||||
while (size > 0) {
|
||||
try {
|
||||
Object[] array = new Object[size];
|
||||
if (first == null) {
|
||||
first = array;
|
||||
} else {
|
||||
last[0] = array;
|
||||
}
|
||||
last = array;
|
||||
} catch (OutOfMemoryError oome) {
|
||||
size = size >>> 1;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
private static void awaitTrue(AtomicBoolean ready) {
|
||||
// Don't call anything that might allocate from the Java heap.
|
||||
while (!ready.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitState(Thread thread, Thread.State expectedState) {
|
||||
// Don't call anything that might allocate from the Java heap.
|
||||
while (thread.getState() != expectedState) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
private static void joinVThread(Thread vthread, AtomicBoolean ready, AtomicReference<Throwable> exRef) throws Throwable {
|
||||
// Don't call anything that might allocate from the Java heap until ready is set.
|
||||
while (!ready.get()) {
|
||||
Throwable ex = exRef.get();
|
||||
if (ex != null) {
|
||||
throw ex;
|
||||
}
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
476
test/jdk/java/lang/Thread/virtual/MonitorPinnedEvents.java
Normal file
476
test/jdk/java/lang/Thread/virtual/MonitorPinnedEvents.java
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test JFR jdk.VirtualThreadPinned event recorded for contended monitor enter
|
||||
* and Object.wait when pinned
|
||||
* @requires vm.continuations & vm.hasJFR
|
||||
* @modules jdk.jfr jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorPinnedEvents
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordedFrame;
|
||||
import jdk.jfr.consumer.RecordedMethod;
|
||||
import jdk.jfr.consumer.RecordedThread;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MonitorPinnedEvents {
|
||||
// log to System.err so inlined with JUnit output
|
||||
private static final PrintStream log = System.err;
|
||||
|
||||
// expected values for "blockingOperation" field in event
|
||||
private static final String CONTENDED_MONITOR_ENTER = "Contended monitor enter";
|
||||
private static final String OBJECT_WAIT = "Object.wait";
|
||||
|
||||
// expected value for "pinnedReason" field in event
|
||||
private static final String NATIVE_FRAME = "Native or VM frame on stack";
|
||||
|
||||
// block or wait for 2s to allow minimum event duration be tested
|
||||
private static final int DELAY = 2000;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need at least two carriers to test pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for a contended monitor enter.
|
||||
*/
|
||||
@Test
|
||||
void testContentedMonitorEnter() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
|
||||
synchronized (lock) {
|
||||
// start virtual thread that blocks trying to acquire monitor while pinned
|
||||
var ready = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
synchronized (lock) { // <--- blocks here while pinned
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready, vthread, Thread.State.BLOCKED);
|
||||
|
||||
// sleep before releasing to ensure virtual thread is blocked for >= 2s
|
||||
Thread.sleep(DELAY);
|
||||
}
|
||||
vthread.join();
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
testEvent(event, vthread, CONTENDED_MONITOR_ENTER, NATIVE_FRAME, DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for a contended monitor enter where
|
||||
* another thread may acquire the monitor when the main thread releases it. If
|
||||
* the other thread acquires the monitor before the virtual thread then the event
|
||||
* duration should include the time blocked until the other thread releases it.
|
||||
*/
|
||||
@Test
|
||||
void testContentedMonitorEnter2() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
// the thread that acquires the monitor after the main thread releases it
|
||||
var nextOwner = new AtomicReference<Thread>();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
Thread thread2;
|
||||
synchronized (lock) {
|
||||
|
||||
// start virtual thread that blocks trying to acquire monitor while pinned
|
||||
var ready1 = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready1.set(true);
|
||||
synchronized (lock) { // <--- blocks here while pinned
|
||||
nextOwner.compareAndSet(null, Thread.currentThread());
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready1, vthread, Thread.State.BLOCKED);
|
||||
|
||||
// start platform thread that blocks trying to acquire monitor
|
||||
thread2 = Thread.ofPlatform().start(() -> {
|
||||
synchronized (lock) {
|
||||
if (nextOwner.compareAndSet(null, Thread.currentThread())) {
|
||||
// delayed release of monitor if acquired before virtual thread
|
||||
try {
|
||||
Thread.sleep(DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} // release lock, vthread or thread2 will acquire
|
||||
|
||||
vthread.join();
|
||||
thread2.join();
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded. If the platform thread
|
||||
// acquired the monitor before the virtual thread then the event duration
|
||||
// should be >= DELAY.
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
Thread winner = nextOwner.get();
|
||||
assertNotNull(winner);
|
||||
int minDuration = winner.isVirtual() ? 0 : DELAY;
|
||||
testEvent(event, vthread, CONTENDED_MONITOR_ENTER, NATIVE_FRAME, minDuration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait when notified.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitNotify(boolean timed) throws Exception {
|
||||
testObjectWait(timed, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait when interrupted.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitInterrupt(boolean timed) throws Exception {
|
||||
testObjectWait(timed, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait.
|
||||
* @param timed true for a timed-wait, false for an untimed-wait
|
||||
* @param interrupt true to interrupt the thread, false to notify
|
||||
*/
|
||||
private void testObjectWait(boolean timed, boolean interrupt) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
|
||||
// start virtual thread that waits in Object.wait while pinned
|
||||
var ready = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
try {
|
||||
// wait while pinned
|
||||
if (timed) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready, vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// sleep to ensure virtual thread waits for >= 2s
|
||||
Thread.sleep(DELAY);
|
||||
|
||||
// interrupt or notify thread so it resumes execution
|
||||
if (interrupt) {
|
||||
vthread.interrupt();
|
||||
} else {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
vthread.join();
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
testEvent(event, vthread, OBJECT_WAIT, NATIVE_FRAME, DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait where the virtual thread
|
||||
* is notified but may block when attempting to reenter. One event should be
|
||||
* recorded. The event duration should include both the waiting time and the time
|
||||
* blocked to reenter.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitNotify2(boolean timed) throws Exception {
|
||||
testObjectWait2(timed, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait where the virtual thread
|
||||
* is interrupted but may block when attempting to reenter. One event should be
|
||||
* recorded. The event duration should include both the waiting time and the time
|
||||
* blocked to reenter.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitInterrupt2(boolean timed) throws Exception {
|
||||
testObjectWait2(timed, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait where the virtual thread
|
||||
* blocks when attempting to reenter.
|
||||
* @param timed true for a timed-wait, false for an untimed-wait
|
||||
* @param interrupt true to interrupt the thread, false to notify
|
||||
*/
|
||||
private void testObjectWait2(boolean timed, boolean interrupt) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
// the thread that acquires the monitor after the main thread releases it
|
||||
var nextOwner = new AtomicReference<Thread>();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
|
||||
// start virtual thread that waits in Object.wait while pinned
|
||||
var ready1 = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
boolean notify = !interrupt;
|
||||
synchronized (lock) {
|
||||
ready1.set(true);
|
||||
try {
|
||||
// wait while pinned
|
||||
if (timed) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
if (notify) {
|
||||
nextOwner.compareAndSet(null, Thread.currentThread());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (interrupt) {
|
||||
nextOwner.compareAndSet(null, Thread.currentThread());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready1, vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// platform thread that blocks on monitor enter
|
||||
Thread thread2;
|
||||
synchronized (lock) {
|
||||
thread2 = Thread.ofPlatform().start(() -> {
|
||||
synchronized (lock) { // <--- blocks here
|
||||
if (nextOwner.compareAndSet(null, Thread.currentThread())) {
|
||||
// delayed release of monitor if acquired before virtual thread
|
||||
try {
|
||||
Thread.sleep(DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// interrupt/notify and release monitor to allow one of the threads to acquire
|
||||
if (interrupt) {
|
||||
vthread.interrupt();
|
||||
} else {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
vthread.join();
|
||||
thread2.join();
|
||||
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded. If the platform thread
|
||||
// acquired the monitor before the virtual thread re-entered then the event
|
||||
// duration should be >= DELAY.
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
Thread winner = nextOwner.get();
|
||||
assertNotNull(winner);
|
||||
int minDuration = winner.isVirtual() ? 0 : DELAY;
|
||||
testEvent(event, vthread, OBJECT_WAIT, NATIVE_FRAME, minDuration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event was recorded by the expected virtual thread, the event has a
|
||||
* "carrierThread", the "blockingOperation", and "pinnedReason" fields have the
|
||||
* expected values, and the event duration is >= minDuration.
|
||||
*/
|
||||
private void testEvent(RecordedEvent event,
|
||||
Thread vthread,
|
||||
String expectedBlockingOp,
|
||||
String expectedPinnedReason,
|
||||
int minDuration) {
|
||||
assertTrue(vthread.isVirtual());
|
||||
assertEquals(vthread.threadId(), event.getThread().getId());
|
||||
|
||||
RecordedThread carrier = event.getValue("carrierThread");
|
||||
assertFalse(carrier.isVirtual());
|
||||
|
||||
assertEquals(expectedBlockingOp, event.getString("blockingOperation"));
|
||||
assertEquals(expectedPinnedReason, event.getString("pinnedReason"));
|
||||
|
||||
long duration = event.getDuration().toMillis();
|
||||
assertTrue(duration >= (minDuration - 100),
|
||||
"Duration " + duration + "ms, expected >= " + minDuration + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the expected jdk.VirtualThreadPinned event in the recording. There may be
|
||||
* several pinned events recorded by other parts of the system that need to be
|
||||
* filtered out.
|
||||
*/
|
||||
private RecordedEvent findPinnedEvent(Recording recording) throws IOException {
|
||||
Map<Boolean, List<RecordedEvent>> events = find(recording, "jdk.VirtualThreadPinned")
|
||||
.collect(Collectors.partitioningBy(e -> filtered(topFrameMethod(e)), Collectors.toList()));
|
||||
List<RecordedEvent> filteredEvents = events.get(Boolean.TRUE);
|
||||
List<RecordedEvent> pinnedEvents = events.get(Boolean.FALSE);
|
||||
log.format("%d event(s) recorded%n", filteredEvents.size() + pinnedEvents.size());
|
||||
log.println("-- filtered events --");
|
||||
log.println(filteredEvents);
|
||||
log.println("-- remaining pinned events --");
|
||||
log.println(pinnedEvents);
|
||||
assertEquals(1, pinnedEvents.size());
|
||||
return pinnedEvents.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pinned events recorded at the following classes/methods should be ignored.
|
||||
*/
|
||||
private final Set<String> FILTERED = Set.of(
|
||||
"java.lang.VirtualThread.*",
|
||||
"java.lang.ref.ReferenceQueue.poll",
|
||||
"java.lang.invoke.*",
|
||||
"jdk.internal.ref.*"
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the given top-frame {@code class.method} should be ignored.
|
||||
*/
|
||||
private boolean filtered(String topFrameMethod) {
|
||||
for (String s : FILTERED) {
|
||||
if (s.endsWith("*")) {
|
||||
String prefix = s.substring(0, s.length()-1);
|
||||
if (topFrameMethod.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
} else if (s.equals(topFrameMethod)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code class.method} of the top frame in the event's stack trace.
|
||||
*/
|
||||
private String topFrameMethod(RecordedEvent event) {
|
||||
RecordedFrame topFrame = event.getStackTrace().getFrames().get(0);
|
||||
RecordedMethod method = topFrame.getMethod();
|
||||
return method.getType().getName() + "." + method.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the events with the given name in the recording.
|
||||
*/
|
||||
private Stream<RecordedEvent> find(Recording recording, String name) throws IOException {
|
||||
Path recordingFile = recording.getDestination();
|
||||
if (recordingFile == null) {
|
||||
ProcessHandle h = ProcessHandle.current();
|
||||
recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
|
||||
recording.dump(recordingFile);
|
||||
}
|
||||
return RecordingFile.readAllEvents(recordingFile)
|
||||
.stream()
|
||||
.filter(e -> e.getEventType().getName().equals(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for ready to become true, then waits for the thread to get to the given state.
|
||||
*/
|
||||
private void await(AtomicBoolean ready,
|
||||
Thread thread,
|
||||
Thread.State expectedState) throws InterruptedException {
|
||||
while (!ready.get()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
803
test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
Normal file
803
test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads using Object.wait/notifyAll
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xint --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:-TieredCompilation --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class MonitorWaitNotify {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for testing pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread waits, notified by platform thread.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testWaitNotify1(boolean pinned) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
awaitTrue(ready);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test platform thread waits, notified by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testWaitNotify2() throws Exception {
|
||||
var lock = new Object();
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
});
|
||||
synchronized (lock) {
|
||||
thread.start();
|
||||
lock.wait();
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread waits, notified by another virtual thread.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testWaitNotify3(boolean pinned) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
var thread2 = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
awaitTrue(ready);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread1, Thread.State.BLOCKED);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notifyAll when there are no threads waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testNotifyBeforeWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
// no threads waiting
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
|
||||
// thread should wait
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
Thread.State expectedState = timeout > 0
|
||||
? Thread.State.TIMED_WAITING
|
||||
: Thread.State.WAITING;
|
||||
await(thread, expectedState);
|
||||
|
||||
// poll thread state again, it should still be waiting
|
||||
Thread.sleep(10);
|
||||
assertEquals(thread.getState(), expectedState);
|
||||
} finally {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of elements that are ordered pairs of platform and virtual thread
|
||||
* counts. 0,2,4,..8 platform threads. 2,4,6,..16 virtual threads.
|
||||
*/
|
||||
static Stream<Arguments> threadCounts() {
|
||||
return IntStream.range(0, 9)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(i -> i)
|
||||
.flatMap(np -> IntStream.range(2, 17)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(vp -> Arguments.of(np, vp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notify wakes only one thread when platform and virtual threads are waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testNotifyOneThread(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
int nThreads = nPlatformThreads + nVirtualThreads;
|
||||
|
||||
var lock = new Object();
|
||||
var ready = new CountDownLatch(nThreads);
|
||||
var notified = new AtomicInteger();
|
||||
|
||||
Runnable waitTask = () -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.countDown();
|
||||
lock.wait();
|
||||
notified.incrementAndGet();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
};
|
||||
|
||||
var threads = new ArrayList<Thread>();
|
||||
try {
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads.add(Thread.ofPlatform().start(waitTask));
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads.add(Thread.ofVirtual().start(waitTask));
|
||||
}
|
||||
|
||||
// wait for all threads to wait
|
||||
ready.await();
|
||||
|
||||
// wake threads, one by one
|
||||
for (int i = 0; i < threads.size(); i++) {
|
||||
|
||||
// wake one thread
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
|
||||
// one thread should have awoken
|
||||
int expectedWakeups = i + 1;
|
||||
while (notified.get() < expectedWakeups) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
assertEquals(expectedWakeups, notified.get());
|
||||
}
|
||||
} finally {
|
||||
for (Thread t : threads) {
|
||||
t.interrupt();
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notifyAll wakes all threads.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testNotifyAllThreads(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
int nThreads = nPlatformThreads + nVirtualThreads;
|
||||
|
||||
var lock = new Object();
|
||||
var ready = new CountDownLatch(nThreads);
|
||||
var notified = new CountDownLatch(nThreads);
|
||||
|
||||
Runnable waitTask = () -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.countDown();
|
||||
lock.wait();
|
||||
notified.countDown();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
};
|
||||
|
||||
var threads = new ArrayList<Thread>();
|
||||
try {
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads.add(Thread.ofPlatform().start(waitTask));
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads.add(Thread.ofVirtual().start(waitTask));
|
||||
}
|
||||
|
||||
// wait for all threads to wait
|
||||
ready.await();
|
||||
|
||||
// wakeup all threads
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// wait for all threads to have awoken
|
||||
notified.await();
|
||||
|
||||
} finally {
|
||||
for (Thread t : threads) {
|
||||
t.interrupt();
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duration of timed Object.wait.
|
||||
*/
|
||||
@Test
|
||||
void testTimedWaitDuration1() throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
var durationRef = new AtomicReference<Long>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
long start = millisTime();
|
||||
lock.wait(2000);
|
||||
durationRef.set(millisTime() - start);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
thread.join();
|
||||
|
||||
long duration = durationRef.get();
|
||||
checkDuration(duration, 1900, 20_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duration of timed Object.wait. This test invokes wait twice, first with a short
|
||||
* timeout, the second with a longer timeout. The test scenario ensures that the
|
||||
* timeout from the first wait doesn't interfere with the second wait.
|
||||
*/
|
||||
@Test
|
||||
void testTimedWaitDuration2() throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var waited = new AtomicBoolean();
|
||||
var durationRef = new AtomicReference<Long>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
lock.wait(200);
|
||||
waited.set(true);
|
||||
|
||||
long start = millisTime();
|
||||
lock.wait(2000);
|
||||
durationRef.set(millisTime() - start);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
awaitTrue(ready);
|
||||
synchronized (lock) {
|
||||
// wake thread if waiting in first wait
|
||||
if (!waited.get()) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
thread.join();
|
||||
|
||||
long duration = durationRef.get();
|
||||
checkDuration(duration, 1900, 20_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing invoking Object.wait with interrupted status set.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testWaitWithInterruptSet(int timeout) throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
Thread.currentThread().interrupt();
|
||||
if (timeout > 0) {
|
||||
assertThrows(InterruptedException.class, () -> lock.wait(timeout));
|
||||
} else {
|
||||
assertThrows(InterruptedException.class, lock::wait);
|
||||
}
|
||||
assertFalse(Thread.currentThread().isInterrupted());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interrupting a virtual thread waiting in Object.wait.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testInterruptWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var interruptedException = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// check stack trace has the expected frames
|
||||
Set<String> expected = Set.of("wait0", "wait", "run");
|
||||
Set<String> methods = Stream.of(e.getStackTrace())
|
||||
.map(StackTraceElement::getMethodName)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(methods.containsAll(expected));
|
||||
|
||||
interruptedException.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// interrupt thread, should block, then throw InterruptedException
|
||||
synchronized (lock) {
|
||||
vthread.interrupt();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
vthread.join();
|
||||
assertTrue(interruptedException.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interrupting a virtual thread blocked waiting to reenter after waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testInterruptReenterAfterWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var interruptedException = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
interruptedException.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
// interrupt when blocked
|
||||
vthread.interrupt();
|
||||
}
|
||||
|
||||
vthread.join();
|
||||
assertFalse(interruptedException.get());
|
||||
assertTrue(vthread.isInterrupted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Object.wait when the monitor entry count > 1.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testWaitWhenEnteredManyTimes(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
synchronized (lock) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
vthread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait does not consume the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotConsumed() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var completed = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
LockSupport.park(); // should not park
|
||||
completed.set(true);
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// wakeup thread
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// thread should terminate
|
||||
vthread.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait does not make available the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotOffered() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var readyToPark = new CountDownLatch(1);
|
||||
var completed = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
readyToPark.countDown();
|
||||
LockSupport.park(); // should park
|
||||
completed.set(true);
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// wakeup thread
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// thread should park
|
||||
readyToPark.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
LockSupport.unpark(vthread);
|
||||
|
||||
// thread should terminate
|
||||
vthread.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait releases the carrier. This test uses a custom scheduler
|
||||
* with one carrier thread.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testReleaseWhenWaiting1(int timeout) throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var completed = new AtomicBoolean();
|
||||
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
completed.set(true);
|
||||
});
|
||||
vthread1.start();
|
||||
|
||||
// wait for vthread1 to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread1, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
|
||||
// wakeup vthread1
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
vthread1.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait releases the carrier. This test arranges for 4*ncores - 1
|
||||
* virtual threads to wait. For long timeout and no timeout cases, all virtual threads
|
||||
* will wait until they are notified.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 10, 20, 100, 500, 30000, Integer.MAX_VALUE })
|
||||
void testReleaseWhenWaiting2(int timeout) throws Exception {
|
||||
int VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
|
||||
CountDownLatch latch = new CountDownLatch(VTHREAD_COUNT);
|
||||
Object lock = new Object();
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
for (int i = 0; i < VTHREAD_COUNT; i++) {
|
||||
Thread.ofVirtual().name("vthread-" + i).start(() -> {
|
||||
synchronized (lock) {
|
||||
if (counter.incrementAndGet() == VTHREAD_COUNT) {
|
||||
lock.notifyAll();
|
||||
} else {
|
||||
try {
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
latch.await();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that wait(long) throws IAE when timeout is negative.
|
||||
*/
|
||||
@Test
|
||||
void testIllegalArgumentException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object obj = new Object();
|
||||
synchronized (obj) {
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L));
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L));
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that wait throws IMSE when not owner.
|
||||
*/
|
||||
@Test
|
||||
void testIllegalMonitorStateException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object obj = new Object();
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait());
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0));
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000));
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time in milliseconds.
|
||||
*/
|
||||
private static long millisTime() {
|
||||
long now = System.nanoTime();
|
||||
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a duration is within expected bounds.
|
||||
* @param duration, in milliseconds
|
||||
* @param min minimum expected duration, in milliseconds
|
||||
* @param max maximum expected duration, in milliseconds
|
||||
* @return the duration (now - start), in milliseconds
|
||||
*/
|
||||
private static void checkDuration(long duration, long min, long max) {
|
||||
assertTrue(duration >= min,
|
||||
"Duration " + duration + "ms, expected >= " + min + "ms");
|
||||
assertTrue(duration <= max,
|
||||
"Duration " + duration + "ms, expected <= " + max + "ms");
|
||||
}
|
||||
}
|
||||
102
test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java
Normal file
102
test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (C) 2021, Tencent. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test virtual thread park when scheduler is a fixed thread pool
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main ParkWithFixedThreadPool
|
||||
*/
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
public class ParkWithFixedThreadPool {
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (var scheduler = new Scheduler(8)) {
|
||||
int vthreadCount = 300;
|
||||
Thread[] vthreads = new Thread[vthreadCount];
|
||||
Runnable target = new Runnable() {
|
||||
public void run() {
|
||||
int myIndex = -1;
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
if (vthreads[i] == Thread.currentThread()) {
|
||||
myIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (myIndex > 0) {
|
||||
LockSupport.unpark(vthreads[myIndex - 1]);
|
||||
}
|
||||
|
||||
if (myIndex != (vthreadCount - 1)) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i] = factory.newThread(target);
|
||||
}
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i].start();
|
||||
}
|
||||
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Scheduler implements Executor, AutoCloseable {
|
||||
private final ExecutorService pool;
|
||||
|
||||
Scheduler(int poolSize) {
|
||||
pool = Executors.newFixedThreadPool(poolSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
try {
|
||||
pool.execute(task);
|
||||
} finally {
|
||||
// ExecutorService::execute may consume parking permit
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
487
test/jdk/java/lang/Thread/virtual/Parking.java
Normal file
487
test/jdk/java/lang/Thread/virtual/Parking.java
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads using park/unpark
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/timeout=480 Parking
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/timeout=480 -Xint Parking
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/timeout=480 -Xcomp Parking
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation Parking
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class Parking {
|
||||
static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
|
||||
static final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Park, unparked by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark1() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||
Thread.sleep(1000); // give time for virtual thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park, unparked by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark2() throws Exception {
|
||||
var thread1 = Thread.ofVirtual().start(LockSupport::park);
|
||||
Thread.sleep(1000); // give time for virtual thread to park
|
||||
var thread2 = Thread.ofVirtual().start(() -> LockSupport.unpark(thread1));
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park while holding monitor, unparked by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark3() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
Thread.sleep(1000); // give time for virtual thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with native frame on stack.
|
||||
*/
|
||||
@Test
|
||||
void testPark4() throws Exception {
|
||||
// not implemented
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpark before park.
|
||||
*/
|
||||
@Test
|
||||
void testPark5() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
LockSupport.park();
|
||||
});
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* 2 x unpark before park.
|
||||
*/
|
||||
@Test
|
||||
void testPark6() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
Thread me = Thread.currentThread();
|
||||
LockSupport.unpark(me);
|
||||
LockSupport.unpark(me);
|
||||
LockSupport.park();
|
||||
LockSupport.park(); // should park
|
||||
});
|
||||
Thread.sleep(1000); // give time for thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* 2 x park and unpark by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark7() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.park();
|
||||
LockSupport.park();
|
||||
});
|
||||
|
||||
Thread.sleep(1000); // give time for thread to park
|
||||
|
||||
// unpark, virtual thread should park again
|
||||
LockSupport.unpark(thread);
|
||||
Thread.sleep(1000);
|
||||
assertTrue(thread.isAlive());
|
||||
|
||||
// let it terminate
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testPark8() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
LockSupport.park();
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked.
|
||||
*/
|
||||
@Test
|
||||
void testPark9() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Park while holding monitor and with interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testPark10() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked while holding monitor
|
||||
*/
|
||||
@Test
|
||||
void testPark11() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* parkNanos(-1) completes immediately
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos1() throws Exception {
|
||||
VThreadRunner.run(() -> LockSupport.parkNanos(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* parkNanos(0) completes immediately
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos2() throws Exception {
|
||||
VThreadRunner.run(() -> LockSupport.parkNanos(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* parkNanos(1000ms) parks thread.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
// park for 1000ms
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1000, TimeUnit.MILLISECONDS);
|
||||
long start = System.nanoTime();
|
||||
LockSupport.parkNanos(nanos);
|
||||
|
||||
// check that virtual thread parked for >= 900ms
|
||||
long elapsed = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start,
|
||||
TimeUnit.NANOSECONDS);
|
||||
assertTrue(elapsed >= 900);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos, unparked by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos4() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
|
||||
LockSupport.parkNanos(nanos);
|
||||
});
|
||||
Thread.sleep(100); // give time for virtual thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos, unparked by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos5() throws Exception {
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
|
||||
LockSupport.parkNanos(nanos);
|
||||
});
|
||||
Thread.sleep(100); // give time for virtual thread to park
|
||||
var thread2 = Thread.ofVirtual().start(() -> LockSupport.unpark(thread1));
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpark before parkNanos.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos6() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
|
||||
LockSupport.parkNanos(nanos);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpark before parkNanos(0), should consume parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos7() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
LockSupport.parkNanos(0); // should consume parking permit
|
||||
LockSupport.park(); // should block
|
||||
});
|
||||
boolean isAlive = thread.join(Duration.ofSeconds(2));
|
||||
assertTrue(isAlive);
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos and interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos8() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked in parkNanos.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos9() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos while holding monitor and with interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos10() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
synchronized (lock) {
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
}
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked in parkNanos and while holding monitor.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos11() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
synchronized (lock) {
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that parking while holding a monitor releases the carrier.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testParkWhenHoldingMonitor(boolean reenter) throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = new Object();
|
||||
|
||||
// thread enters (and maybe reenters) a monitor and parks
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
if (reenter) {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
vthread1.start();
|
||||
try {
|
||||
// wait for thread to start and park
|
||||
started.await();
|
||||
await(vthread1, Thread.State.WAITING);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
} finally {
|
||||
LockSupport.unpark(vthread1);
|
||||
vthread1.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lots of virtual threads parked while holding a monitor. If the number of
|
||||
* virtual threads exceeds the number of carrier threads then this test will hang if
|
||||
* parking doesn't release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testManyParkedWhenHoldingMonitor() throws Exception {
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
var done = new AtomicBoolean();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
// wait for thread to start and park
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
vthreads[i] = vthread;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
done.set(true);
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
var vthread = vthreads[i];
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a thread to be interrupted after a delay.
|
||||
*/
|
||||
private static void scheduleInterrupt(Thread thread, long delay) {
|
||||
Runnable interruptTask = () -> {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
thread.interrupt();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
new Thread(interruptTask).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
393
test/jdk/java/lang/Thread/virtual/Reflection.java
Normal file
393
test/jdk/java/lang/Thread/virtual/Reflection.java
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test virtual threads using core reflection
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit Reflection
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class Reflection {
|
||||
|
||||
/**
|
||||
* Test invoking static method.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
int result = (int) divideMethod().invoke(null, 20, 2);
|
||||
assertTrue(result == 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that InvocationTargetException is thrown when a static method throws
|
||||
* exception.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
try {
|
||||
divideMethod().invoke(null, 20, 0);
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(e.getCause() instanceof ArithmeticException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that IllegalArgumentException is thrown when trying to invoke a static
|
||||
* method with bad parameters.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(null, 1));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(null, 1, 2, 3));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(new Object()));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(new Object(), 1));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(new Object(), 1, 2, 3));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ExceptionInInitializerError is thrown when invoking a static
|
||||
* method triggers its class to be initialized and it fails with exception.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic4() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Method foo = BadClass1.class.getDeclaredMethod("foo");
|
||||
try {
|
||||
foo.invoke(null);
|
||||
fail();
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
assertTrue(e.getCause() instanceof ArithmeticException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass1 {
|
||||
static {
|
||||
if (1==1) throw new ArithmeticException();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an error is thrown when invoking a static method triggers its
|
||||
* class to be initialized and it fails with an error.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic5() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Method foo = BadClass2.class.getDeclaredMethod("foo");
|
||||
assertThrows(AbstractMethodError.class, () -> foo.invoke(null));
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass2 {
|
||||
static {
|
||||
if (1==1) throw new AbstractMethodError();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that invoking a static method does not pin the carrier thread.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic6() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Method parkMethod = Parker.class.getDeclaredMethod("park");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new CountDownLatch(1);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
ready.countDown();
|
||||
try {
|
||||
parkMethod.invoke(null); // blocks
|
||||
} catch (Exception e) { }
|
||||
});
|
||||
vthread.start();
|
||||
|
||||
try {
|
||||
// wait for thread to run
|
||||
ready.await();
|
||||
|
||||
// unpark with another virtual thread, runs on same carrier thread
|
||||
Thread unparker = factory.newThread(() -> LockSupport.unpark(vthread));
|
||||
unparker.start();
|
||||
unparker.join();
|
||||
} finally {
|
||||
LockSupport.unpark(vthread); // in case test fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invoking instance method.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeInstance1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
Adder.addMethod().invoke(adder, 5);
|
||||
assertTrue(adder.sum == 5);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that InvocationTargetException is thrown when an instance method throws
|
||||
* exception.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeInstance2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
try {
|
||||
Adder.addMethod().invoke(adder, -5);
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that NullPointerException and IllegalArgumentException are thrown when
|
||||
* trying to invoke an instance method with null or bad parameters.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeInstance3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
Method addMethod = Adder.addMethod();
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> addMethod.invoke(null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder, 1, 2));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder, 1, "hi"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder, "hi"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invoking newInstance to create an object.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = Adder.class.getDeclaredConstructor(long.class);
|
||||
Adder adder = (Adder) ctor.newInstance(10);
|
||||
assertTrue(adder.sum == 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that InvocationTargetException is thrown when a constructor throws
|
||||
* exception.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = Adder.class.getDeclaredConstructor(long.class);
|
||||
try {
|
||||
ctor.newInstance(-10);
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that IllegalArgumentException is thrown when newInstacne is called
|
||||
* with bad parameters.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
Constructor<?> ctor = Adder.class.getDeclaredConstructor(long.class);
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance((Object[])null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder, null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder, "foo"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder, 1, 2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ExceptionInInitializerError is thrown when invoking newInstance
|
||||
* triggers the class to be initialized and it fails with exception.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance4() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = BadClass3.class.getDeclaredConstructor();
|
||||
try {
|
||||
ctor.newInstance((Object[])null);
|
||||
fail();
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
assertTrue(e.getCause() instanceof ArithmeticException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass3 {
|
||||
static {
|
||||
if (1==1) throw new ArithmeticException();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that error is thrown when invoking newInstance triggers the class
|
||||
* to be initialized and it fails with an error.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance5() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = BadClass4.class.getDeclaredConstructor();
|
||||
assertThrows(AbstractMethodError.class, () -> ctor.newInstance((Object[])null));
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass4 {
|
||||
static {
|
||||
if (1==1) throw new AbstractMethodError();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that newInstance does not pin the carrier thread
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance6() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Constructor<?> ctor = Parker.class.getDeclaredConstructor();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new CountDownLatch(1);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
ready.countDown();
|
||||
try {
|
||||
ctor.newInstance();
|
||||
} catch (Exception e) { }
|
||||
});
|
||||
vthread.start();
|
||||
|
||||
try {
|
||||
// wait for thread to run
|
||||
ready.await();
|
||||
|
||||
// unpark with another virtual thread, runs on same carrier thread
|
||||
Thread unparker = factory.newThread(() -> LockSupport.unpark(vthread));
|
||||
unparker.start();
|
||||
unparker.join();
|
||||
} finally {
|
||||
LockSupport.unpark(vthread); // in case test fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -- support classes and methods --
|
||||
|
||||
static int divide(int x, int y) {
|
||||
return x / y;
|
||||
}
|
||||
|
||||
static Method divideMethod() throws NoSuchMethodException {
|
||||
return Reflection.class.getDeclaredMethod("divide", int.class, int.class);
|
||||
}
|
||||
|
||||
static class Adder {
|
||||
long sum;
|
||||
Adder() { }
|
||||
Adder(long x) {
|
||||
if (x < 0)
|
||||
throw new IllegalArgumentException();
|
||||
sum = x;
|
||||
}
|
||||
Adder add(long x) {
|
||||
if (x < 0)
|
||||
throw new IllegalArgumentException();
|
||||
sum += x;
|
||||
return this;
|
||||
}
|
||||
static Method addMethod() throws NoSuchMethodException {
|
||||
return Adder.class.getDeclaredMethod("add", long.class);
|
||||
}
|
||||
long sum() {
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
static class Parker {
|
||||
Parker() {
|
||||
LockSupport.park();
|
||||
}
|
||||
static void park() {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test that a virtual thread waiting to enter/reenter a monitor, while pinning
|
||||
* its carrier, will retry until it enters the monitor. This avoids starvation when the
|
||||
* monitor is exited, an unmounted thread is the chosen successor, and the successor
|
||||
* can't continue because there are no carriers available.
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED RetryMonitorEnterWhenPinned
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
public class RetryMonitorEnterWhenPinned {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for testing pinning when main thread is a virtual thread
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
@RepeatedTest(10)
|
||||
void testMonitorEnter() throws Exception {
|
||||
var threads = new ArrayList<Thread>();
|
||||
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
|
||||
// start virtual threads that block on monitorenter
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
spin(20);
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// start virtual threads that block on monitorenter while pinned
|
||||
int carriersAvailable = Runtime.getRuntime().availableProcessors();
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
carriersAvailable--;
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
started.countDown();
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
spin(20);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// if there are carriers available when wait until the thread blocks.
|
||||
if (carriersAvailable > 0) {
|
||||
System.out.printf("%s waiting for thread #%d to block%n",
|
||||
Instant.now(), thread.threadId());
|
||||
started.await();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
carriersAvailable--;
|
||||
}
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
} // exit monitor
|
||||
|
||||
// wait for all threads to terminate
|
||||
int threadsRemaining = threads.size();
|
||||
while (threadsRemaining > 0) {
|
||||
System.out.printf("%s waiting for %d threads to terminate%n",
|
||||
Instant.now(), threadsRemaining);
|
||||
int terminated = 0;
|
||||
for (Thread t : threads) {
|
||||
if (t.join(Duration.ofSeconds(1))) {
|
||||
terminated++;
|
||||
}
|
||||
}
|
||||
threadsRemaining = threads.size() - terminated;
|
||||
}
|
||||
System.out.printf("%s done%n", Instant.now());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testMonitorReenter(int timeout) throws Exception {
|
||||
var threads = new ArrayList<Thread>();
|
||||
|
||||
Object lock = new Object();
|
||||
|
||||
// start virtual threads that wait on Object.wait
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
started.countDown();
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(thread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// start virtual threads that wait on Object.wait while pinnned
|
||||
int carriersAvailable = Runtime.getRuntime().availableProcessors();
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
carriersAvailable--;
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
boolean hasCarrier = carriersAvailable > 0;
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
started.countDown();
|
||||
if (!hasCarrier) {
|
||||
// This thread will run at the very
|
||||
// end and won't be notified.
|
||||
lock.wait(1);
|
||||
} else if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
});
|
||||
|
||||
// if there are carriers available when wait until the thread blocks.
|
||||
if (hasCarrier) {
|
||||
System.out.printf("%s waiting for thread #%d to block%n",
|
||||
Instant.now(), thread.threadId());
|
||||
started.await();
|
||||
await(thread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
carriersAvailable--;
|
||||
}
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// wakeup all threads
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// wait for all threads to terminate
|
||||
int threadsRemaining = threads.size();
|
||||
while (threadsRemaining > 0) {
|
||||
System.out.printf("%s waiting for %d threads to terminate%n",
|
||||
Instant.now(), threadsRemaining);
|
||||
int terminated = 0;
|
||||
for (Thread t : threads) {
|
||||
if (t.join(Duration.ofSeconds(1))) {
|
||||
terminated++;
|
||||
}
|
||||
}
|
||||
threadsRemaining = threads.size() - terminated;
|
||||
}
|
||||
System.out.printf("%s done%n", Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Spin for the given number of milliseconds.
|
||||
*/
|
||||
static void spin(long millis) {
|
||||
long nanos = TimeUnit.MILLISECONDS.toNanos(millis);
|
||||
long start = System.nanoTime();
|
||||
while ((System.nanoTime() - start) < nanos) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a thread to reach the expected state.
|
||||
*/
|
||||
static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assert state != Thread.State.TERMINATED : "Thread has terminated";
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
test/jdk/java/lang/Thread/virtual/ShutdownHook.java
Normal file
53
test/jdk/java/lang/Thread/virtual/ShutdownHook.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @summary Shutdown hook is called with virtual thread
|
||||
* @run main ShutdownHook test
|
||||
*/
|
||||
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import static jdk.test.lib.process.ProcessTools.createTestJavaProcessBuilder;
|
||||
import static jdk.test.lib.process.ProcessTools.executeProcess;
|
||||
|
||||
public class ShutdownHook {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0) {
|
||||
ProcessBuilder pb = createTestJavaProcessBuilder("ShutdownHook");
|
||||
OutputAnalyzer output = executeProcess(pb);
|
||||
output.shouldContain("Shutdown Hook");
|
||||
output.shouldHaveExitValue(0);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread thread = Thread.ofVirtual().unstarted(()-> {
|
||||
System.out.println("Shutdown Hook");
|
||||
System.out.flush();
|
||||
});
|
||||
Runtime.getRuntime().addShutdownHook(thread);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
160
test/jdk/java/lang/Thread/virtual/StackFrames.java
Normal file
160
test/jdk/java/lang/Thread/virtual/StackFrames.java
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test stack traces in exceptions, stack frames walked by the StackWalker,
|
||||
* and the stack trace returned by Thread.getStackTrace
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open java.management
|
||||
* @library /test/lib
|
||||
* @run junit StackFrames
|
||||
* @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowCarrierFrames StackFrames
|
||||
*/
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
import static java.lang.StackWalker.Option.*;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class StackFrames {
|
||||
|
||||
/**
|
||||
* Test that the stack trace in exceptions does not include the carrier thread
|
||||
* frames, except when running with -XX:+ShowCarrierFrames.
|
||||
*/
|
||||
@Test
|
||||
void testStackTraceException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Exception e = new Exception();
|
||||
boolean found = Arrays.stream(e.getStackTrace())
|
||||
.map(StackTraceElement::getClassName)
|
||||
.anyMatch("java.util.concurrent.ForkJoinPool"::equals);
|
||||
assertTrue(found == hasJvmArgument("-XX:+ShowCarrierFrames"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that StackWalker does not include carrier thread frames in the stream of
|
||||
* stack frames.
|
||||
*/
|
||||
@Test
|
||||
void testStackWalker() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
StackWalker walker = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE));
|
||||
boolean found = walker.walk(sf ->
|
||||
sf.map(StackWalker.StackFrame::getDeclaringClass)
|
||||
.anyMatch(c -> c == ForkJoinPool.class));
|
||||
assertFalse(found);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.getStackTrace returns the expected bottom frame for both the carrier
|
||||
* and virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testBottomFrames() throws Exception {
|
||||
try (ForkJoinPool pool = new ForkJoinPool(1)) {
|
||||
var carrierRef = new AtomicReference<Thread>();
|
||||
Executor scheduler = task -> {
|
||||
pool.submit(() -> {
|
||||
carrierRef.set(Thread.currentThread());
|
||||
task.run();
|
||||
});
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
|
||||
// create virtual thread to use custom scheduler
|
||||
var vthread = factory.newThread(() -> {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
|
||||
vthread.start();
|
||||
try {
|
||||
awaitTrue(ready);
|
||||
|
||||
// get carrier Thread
|
||||
Thread carrier = carrierRef.get();
|
||||
assertTrue(carrier instanceof ForkJoinWorkerThread);
|
||||
|
||||
// bottom-most frame of virtual thread should be VirtualThread.run
|
||||
System.err.println(vthread);
|
||||
StackTraceElement[] vthreadStack = vthread.getStackTrace();
|
||||
Stream.of(vthreadStack).forEach(e -> System.err.println(" " + e));
|
||||
StackTraceElement bottomFrame = vthreadStack[vthreadStack.length - 1];
|
||||
assertEquals("java.lang.VirtualThread.run",
|
||||
bottomFrame.getClassName() + "." + bottomFrame.getMethodName());
|
||||
|
||||
// bottom-most frame of carrier thread should be Thread.run
|
||||
System.err.println(carrier);
|
||||
StackTraceElement[] carrierStack = carrier.getStackTrace();
|
||||
Stream.of(carrierStack).forEach(e -> System.err.println(" " + e));
|
||||
bottomFrame = carrierStack[carrierStack.length - 1];
|
||||
assertEquals("java.util.concurrent.ForkJoinWorkerThread.run",
|
||||
bottomFrame.getClassName() + "." + bottomFrame.getMethodName());
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if started with the given VM option.
|
||||
*/
|
||||
private static boolean hasJvmArgument(String arg) {
|
||||
for (String argument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
|
||||
if (argument.equals(arg)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
test/jdk/java/lang/Thread/virtual/Starvation.java
Normal file
92
test/jdk/java/lang/Thread/virtual/Starvation.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @requires vm.continuations
|
||||
* @library /test/lib
|
||||
* @bug 8345294
|
||||
* @run main/othervm/native --enable-native-access=ALL-UNNAMED Starvation
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class Starvation {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
int nprocs = Runtime.getRuntime().availableProcessors();
|
||||
iterations = 40_000 / nprocs;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
System.out.format("%s iteration %d of %d ...%n", Instant.now(), i, iterations);
|
||||
var exRef = new AtomicReference<Exception>();
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
runTest();
|
||||
} catch (Exception e) {
|
||||
exRef.set(e);
|
||||
}
|
||||
});
|
||||
while (!thread.join(Duration.ofSeconds(1))) {
|
||||
System.out.format("%s iteration %d waiting for %s%n", Instant.now(), i, thread);
|
||||
}
|
||||
Exception ex = exRef.get();
|
||||
if (ex != null) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void runTest() throws InterruptedException {
|
||||
int nprocs = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
var threads = new ArrayList<Thread>();
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
for (int i = 0; i < nprocs - 1; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
started.countDown();
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
}
|
||||
});
|
||||
});
|
||||
started.await();
|
||||
threads.add(thread);
|
||||
}
|
||||
}
|
||||
|
||||
for (Thread t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
511
test/jdk/java/lang/Thread/virtual/SynchronizedNative.java
Normal file
511
test/jdk/java/lang/Thread/virtual/SynchronizedNative.java
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads with a synchronized native method and a native method
|
||||
* that enter/exits a monitor with JNI MonitorEnter/MonitorExit
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xint --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:-TieredCompilation --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Phaser;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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 SynchronizedNative {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws Exception {
|
||||
// need at least two carriers to test pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
System.loadLibrary("SynchronizedNative");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with a synchronized native method, no contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnter() throws Exception {
|
||||
Object lock = this;
|
||||
VThreadRunner.run(() -> {
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reentering a monitor with synchronized native method, no contention.
|
||||
*/
|
||||
@Test
|
||||
void testReenter() throws Exception {
|
||||
Object lock = this;
|
||||
VThreadRunner.run(() -> {
|
||||
|
||||
// enter, reenter with a synchronized native method
|
||||
synchronized (lock) {
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with synchronized native method, renter with synchronized statement
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with synchronized native method, reenter with synchronized native method
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with a synchronized native method and with contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWithContention() throws Exception {
|
||||
var lock = this;
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
});
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of elements that are ordered pairs of platform and virtual thread
|
||||
* counts. 0,2,4 platform threads. 2,4,6,8 virtual threads.
|
||||
*/
|
||||
static Stream<Arguments> threadCounts() {
|
||||
return IntStream.range(0, 5)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(i -> i)
|
||||
.flatMap(np -> IntStream.range(2, 9)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(vp -> Arguments.of(np, vp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task concurrently from both platform and virtual threads.
|
||||
*/
|
||||
private void executeConcurrently(int nPlatformThreads,
|
||||
int nVirtualThreads,
|
||||
Runnable task) throws Exception {
|
||||
int parallism = nVirtualThreads;
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
parallism++;
|
||||
}
|
||||
int previousParallelism = VThreadRunner.ensureParallelism(parallism);
|
||||
try {
|
||||
int nthreads = nPlatformThreads + nVirtualThreads;
|
||||
var phaser = new Phaser(nthreads + 1);
|
||||
|
||||
// start all threads
|
||||
var threads = new Thread[nthreads];
|
||||
int index = 0;
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads[index++] = Thread.ofPlatform().start(() -> {
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
task.run();
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads[index++] = Thread.ofVirtual().start(() -> {
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
task.run();
|
||||
});
|
||||
}
|
||||
|
||||
// wait for all threads to start
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
System.err.printf(" %d threads started%n", nthreads);
|
||||
|
||||
// wait for all threads to terminate
|
||||
for (Thread thread : threads) {
|
||||
if (thread != null) {
|
||||
System.err.printf(" join %s ...%n", thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// reset parallelism
|
||||
VThreadRunner.setParallelism(previousParallelism);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test entering a monitor with a synchronized native method from many threads
|
||||
* at the same time.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testEnterConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
var counter = new Object() {
|
||||
int value;
|
||||
int value() { return value; }
|
||||
void increment() { value++; }
|
||||
};
|
||||
var lock = this;
|
||||
executeConcurrently(nPlatformThreads, nVirtualThreads, () -> {
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
counter.increment();
|
||||
LockSupport.parkNanos(100_000_000); // 100ms
|
||||
});
|
||||
});
|
||||
synchronized (lock) {
|
||||
assertEquals(nPlatformThreads + nVirtualThreads, counter.value());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with JNI MonitorEnter.
|
||||
*/
|
||||
@Test
|
||||
void testEnterInNative() throws Exception {
|
||||
Object lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reentering a monitor with JNI MonitorEnter.
|
||||
*/
|
||||
@Test
|
||||
void testReenterInNative() throws Exception {
|
||||
Object lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
|
||||
// enter, reenter with JNI MonitorEnter
|
||||
synchronized (lock) {
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with JNI MonitorEnter, renter with synchronized statement
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with JNI MonitorEnter, renter with JNI MonitorEnter
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with JNI MonitorEnter and with contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnterInNativeWithContention() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
});
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with JNI MonitorEnter from many threads at the same time.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testEnterInNativeConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
var counter = new Object() {
|
||||
int value;
|
||||
int value() { return value; }
|
||||
void increment() { value++; }
|
||||
};
|
||||
var lock = counter;
|
||||
executeConcurrently(nPlatformThreads, nVirtualThreads, () -> {
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
counter.increment();
|
||||
LockSupport.parkNanos(100_000_000); // 100ms
|
||||
});
|
||||
});
|
||||
synchronized (lock) {
|
||||
assertEquals(nPlatformThreads + nVirtualThreads, counter.value());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test parking with synchronized native method on stack.
|
||||
*/
|
||||
@Test
|
||||
void testParkingWhenPinned() throws Exception {
|
||||
var lock = this;
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test blocking with synchronized native method on stack.
|
||||
*/
|
||||
@Test
|
||||
void testBlockingWhenPinned() throws Exception {
|
||||
var lock1 = this;
|
||||
var lock2 = new Object();
|
||||
|
||||
var started = new CountDownLatch(1);
|
||||
var entered1 = new AtomicBoolean(); // set to true when vthread enters lock1
|
||||
var entered2 = new AtomicBoolean(); // set to true when vthread enters lock2
|
||||
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock1));
|
||||
entered1.set(true);
|
||||
synchronized (lock2) { // should block
|
||||
assertTrue(Thread.holdsLock(lock2));
|
||||
entered2.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
synchronized (lock2) {
|
||||
// start thread and wait for it to block trying to enter lock2
|
||||
vthread.start();
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertTrue(entered1.get());
|
||||
assertFalse(entered2.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered2.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that blocking on synchronized native method releases the carrier.
|
||||
*/
|
||||
//@Test
|
||||
void testReleaseWhenBlocked() throws Exception {
|
||||
assertTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = this;
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean(); // set to true when vthread enters lock
|
||||
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
|
||||
vthread1.start();
|
||||
try {
|
||||
synchronized (this) {
|
||||
// start thread and wait for it to block
|
||||
vthread1.start();
|
||||
started.await();
|
||||
await(vthread1, Thread.State.BLOCKED);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
}
|
||||
} finally {
|
||||
vthread1.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given task's run method while holding the monitor for "this".
|
||||
*/
|
||||
private synchronized native void runWithSynchronizedNative(Runnable task);
|
||||
|
||||
/**
|
||||
* Invokes the given task's run method while holding the monitor for the given
|
||||
* object. The monitor is entered with JNI MonitorEnter, and exited with JNI MonitorExit.
|
||||
*/
|
||||
private native void runWithMonitorEnteredInNative(Object lock, Runnable task);
|
||||
|
||||
/**
|
||||
* Called from native methods to run the given task.
|
||||
*/
|
||||
private void run(Runnable task) {
|
||||
task.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
2625
test/jdk/java/lang/Thread/virtual/ThreadAPI.java
Normal file
2625
test/jdk/java/lang/Thread/virtual/ThreadAPI.java
Normal file
File diff suppressed because it is too large
Load diff
158
test/jdk/java/lang/Thread/virtual/ThreadLocals.java
Normal file
158
test/jdk/java/lang/Thread/virtual/ThreadLocals.java
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test Virtual threads using thread locals
|
||||
* @library /test/lib
|
||||
* @run junit ThreadLocals
|
||||
*/
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ThreadLocals {
|
||||
static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
|
||||
static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Basic test of thread local set/get.
|
||||
*/
|
||||
@Test
|
||||
void testThreadLocal1() throws Exception {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
VThreadRunner.run(() -> {
|
||||
assertNull(LOCAL.get());
|
||||
Object obj = new Object();
|
||||
LOCAL.set(obj);
|
||||
assertTrue(LOCAL.get() == obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setting thread local before blocking operation.
|
||||
*/
|
||||
@Test
|
||||
void testThreadLocal2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
assertNull(LOCAL.get());
|
||||
Object obj = new Object();
|
||||
LOCAL.set(obj);
|
||||
try { Thread.sleep(100); } catch (InterruptedException e) { }
|
||||
assertTrue(LOCAL.get() == obj);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic test of inheritable thread local set/get, no initial value inherited.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal1() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
VThreadRunner.run(() -> {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
Object obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
});
|
||||
}
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inheriting initial value of InheritableThreadLocal from platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal2() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
try {
|
||||
VThreadRunner.run(() -> {
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
});
|
||||
} finally {
|
||||
INHERITED_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inheriting initial value of InheritableThreadLocal from virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal3() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
VThreadRunner.run(() -> {
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
VThreadRunner.run(() -> {
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
});
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
|
||||
});
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread that does not inherit initial value of InheritableThreadLocals
|
||||
* from platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal4() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
try {
|
||||
int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
|
||||
VThreadRunner.run(characteristics, () -> {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
});
|
||||
} finally {
|
||||
INHERITED_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread that does not inherit initial value of InheritableThreadLocals
|
||||
* from virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal5() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
VThreadRunner.run(() -> {
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
|
||||
VThreadRunner.run(characteristics, () -> {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
});
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
|
||||
});
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
}
|
||||
}
|
||||
83
test/jdk/java/lang/Thread/virtual/ThreadPollOnYield.java
Normal file
83
test/jdk/java/lang/Thread/virtual/ThreadPollOnYield.java
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @bug 8335269
|
||||
* @summary Test that Thread.yield loop polls for safepoints
|
||||
* @requires vm.continuations
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED ThreadPollOnYield
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=c2
|
||||
* @bug 8335269
|
||||
* @summary Test that Thread.yield loop polls for safepoints
|
||||
* @requires vm.continuations & vm.compMode != "Xcomp"
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED -Xcomp -XX:-TieredCompilation
|
||||
* -XX:CompileCommand=inline,*::yield* -XX:CompileCommand=inline,*::*Yield ThreadPollOnYield
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ThreadPollOnYield {
|
||||
private static final CountDownLatch started = new CountDownLatch(1);
|
||||
static void foo(AtomicBoolean done) {
|
||||
started.countDown();
|
||||
while (!done.get()) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testThreadYieldPolls() throws Exception {
|
||||
AtomicBoolean done = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> foo(done));
|
||||
});
|
||||
started.await();
|
||||
done.set(true);
|
||||
vthread.join();
|
||||
|
||||
System.out.println("First vthread done");
|
||||
|
||||
AtomicBoolean done2 = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> foo(done2));
|
||||
});
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < 5000) {
|
||||
Thread.sleep(250);
|
||||
System.gc();
|
||||
}
|
||||
done2.set(true);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test diagnostic option for detecting a virtual thread using thread locals
|
||||
* @requires vm.continuations
|
||||
* @library /test/lib
|
||||
* @run junit/othervm -Djdk.traceVirtualThreadLocals TraceVirtualThreadLocals
|
||||
* @run junit/othervm -Djdk.traceVirtualThreadLocals=true TraceVirtualThreadLocals
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TraceVirtualThreadLocals {
|
||||
|
||||
@Test
|
||||
void testInitialValue() throws Exception {
|
||||
String output = run(() -> {
|
||||
ThreadLocal<String> name = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected String initialValue() {
|
||||
return "<unnamed>";
|
||||
}
|
||||
};
|
||||
name.get();
|
||||
});
|
||||
assertContains(output, "java.lang.ThreadLocal.setInitialValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSet() throws Exception {
|
||||
String output = run(() -> {
|
||||
ThreadLocal<String> name = new ThreadLocal<>();
|
||||
name.set("duke");
|
||||
});
|
||||
assertContains(output, "java.lang.ThreadLocal.set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task in a virtual thread, returning a String with any output printed
|
||||
* to standard output.
|
||||
*/
|
||||
private static String run(Runnable task) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintStream original = System.out;
|
||||
System.setOut(new PrintStream(baos, true));
|
||||
try {
|
||||
VThreadRunner.run(task::run);
|
||||
} finally {
|
||||
System.setOut(original);
|
||||
}
|
||||
String output = new String(baos.toByteArray());
|
||||
System.out.println(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that s1 contains s2.
|
||||
*/
|
||||
private static void assertContains(String s1, String s2) {
|
||||
assertTrue(s1.contains(s2), s2 + " not found!!!");
|
||||
}
|
||||
}
|
||||
133
test/jdk/java/lang/Thread/virtual/YieldQueuing.java
Normal file
133
test/jdk/java/lang/Thread/virtual/YieldQueuing.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test Thread.yield submits the virtual thread task to the expected queue
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm -Djdk.virtualThreadScheduler.maxPoolSize=1 YieldQueuing
|
||||
*/
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class YieldQueuing {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws Exception {
|
||||
// waiting for LockSupport to be initialized can change the scheduling
|
||||
MethodHandles.lookup().ensureInitialized(LockSupport.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.yield submits the task for the current virtual thread to a scheduler
|
||||
* submission queue when there are no tasks in the local queue.
|
||||
*/
|
||||
@Test
|
||||
void testYieldWithEmptyLocalQueue() throws Exception {
|
||||
var list = new CopyOnWriteArrayList<String>();
|
||||
|
||||
var threadsStarted = new AtomicBoolean();
|
||||
|
||||
var threadA = Thread.ofVirtual().unstarted(() -> {
|
||||
// pin thread until task for B is in submission queue
|
||||
while (!threadsStarted.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
|
||||
list.add("A");
|
||||
Thread.yield(); // push task for A to submission queue, B should run
|
||||
list.add("A");
|
||||
});
|
||||
|
||||
var threadB = Thread.ofVirtual().unstarted(() -> {
|
||||
list.add("B");
|
||||
});
|
||||
|
||||
// push tasks for A and B to submission queue
|
||||
threadA.start();
|
||||
threadB.start();
|
||||
|
||||
// release A
|
||||
threadsStarted.set(true);
|
||||
|
||||
// wait for result
|
||||
threadA.join();
|
||||
threadB.join();
|
||||
assertEquals(list, List.of("A", "B", "A"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.yield submits the task for the current virtual thread to the local
|
||||
* queue when there are tasks in the local queue.
|
||||
*/
|
||||
@Test
|
||||
void testYieldWithNonEmptyLocalQueue() throws Exception {
|
||||
var list = new CopyOnWriteArrayList<String>();
|
||||
|
||||
var threadsStarted = new AtomicBoolean();
|
||||
|
||||
var threadA = Thread.ofVirtual().unstarted(() -> {
|
||||
// pin thread until tasks for B and C are in submission queue
|
||||
while (!threadsStarted.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
|
||||
list.add("A");
|
||||
LockSupport.park(); // B should run
|
||||
list.add("A");
|
||||
});
|
||||
|
||||
var threadB = Thread.ofVirtual().unstarted(() -> {
|
||||
list.add("B");
|
||||
LockSupport.unpark(threadA); // push task for A to local queue
|
||||
Thread.yield(); // push task for B to local queue, A should run
|
||||
list.add("B");
|
||||
});
|
||||
|
||||
var threadC = Thread.ofVirtual().unstarted(() -> {
|
||||
list.add("C");
|
||||
});
|
||||
|
||||
// push tasks for A, B and C to submission queue
|
||||
threadA.start();
|
||||
threadB.start();
|
||||
threadC.start();
|
||||
|
||||
// release A
|
||||
threadsStarted.set(true);
|
||||
|
||||
// wait for result
|
||||
threadA.join();
|
||||
threadB.join();
|
||||
threadC.join();
|
||||
assertEquals(list, List.of("A", "B", "A", "B", "C"));
|
||||
}
|
||||
}
|
||||
43
test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c
Normal file
43
test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_SynchronizedNative_runWithSynchronizedNative(JNIEnv *env, jobject obj, jobject task) {
|
||||
jclass clazz = (*env)->GetObjectClass(env, obj);
|
||||
jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
|
||||
if (mid != NULL) {
|
||||
(*env)->CallVoidMethod(env, obj, mid, task);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_SynchronizedNative_runWithMonitorEnteredInNative(JNIEnv *env, jobject obj, jobject lock, jobject task) {
|
||||
jclass clazz = (*env)->GetObjectClass(env, obj);
|
||||
jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
|
||||
if (mid != NULL && (*env)->MonitorEnter(env, lock) == 0) {
|
||||
(*env)->CallVoidMethod(env, obj, mid, task);
|
||||
(*env)->MonitorExit(env, lock); // can be called with pending exception
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress parking with CompletableFuture timed get
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/othervm -Xmx1g CompletableFutureTimedGet 100000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CompletableFutureTimedGet {
|
||||
|
||||
static final String RESULT = "foo";
|
||||
|
||||
public static void main(String... args) throws InterruptedException {
|
||||
int threadCount = 250_000;
|
||||
if (args.length > 0) {
|
||||
threadCount = Integer.parseInt(args[0]);
|
||||
}
|
||||
|
||||
// the count of the number of threads that complete successfully
|
||||
AtomicInteger completed = new AtomicInteger();
|
||||
|
||||
// list of futures and threads
|
||||
List<CompletableFuture<String>> futures = new ArrayList<>();
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
|
||||
// start threads that wait with timeout for a result
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
var future = new CompletableFuture<String>();
|
||||
futures.add(future);
|
||||
|
||||
// start a thread that uses a timed-get to wait for the result
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
String result = future.get(1, TimeUnit.DAYS);
|
||||
if (!RESULT.equals(result)) {
|
||||
throw new RuntimeException("result=" + result);
|
||||
}
|
||||
completed.incrementAndGet();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// sets the result, which will unpark waiting threads
|
||||
futures.forEach(f -> f.complete(RESULT));
|
||||
|
||||
// wait for all threads to terminate
|
||||
long lastTimestamp = System.currentTimeMillis();
|
||||
boolean done;
|
||||
do {
|
||||
done = true;
|
||||
for (Thread t : threads) {
|
||||
if (!t.join(Duration.ofSeconds(1))) {
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
|
||||
// print trace message so the output tracks progress
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (done || ((currentTime - lastTimestamp) > 500)) {
|
||||
System.out.format("%s => completed %d of %d%n",
|
||||
Instant.now(), completed.get(), threadCount);
|
||||
lastTimestamp = currentTime;
|
||||
}
|
||||
|
||||
} while (!done);
|
||||
|
||||
// all tasks should have completed successfully
|
||||
int completedCount = completed.get();
|
||||
if (completedCount != threadCount) {
|
||||
throw new RuntimeException("completed = " + completedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.getStackTrace on virtual threads that are blocking or
|
||||
* blocked on monitorenter
|
||||
* @requires vm.debug != true
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 GetStackTraceALotWhenBlocking 10000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 GetStackTraceALotWhenBlocking 5000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import jdk.test.lib.Platform;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
|
||||
public class GetStackTraceALotWhenBlocking {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// need at least two carriers
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
|
||||
int iterations;
|
||||
int value = Integer.parseInt(args[0]);
|
||||
if (Platform.isOSX()) {
|
||||
// reduced iterations on macosx
|
||||
iterations = Math.max(value / 4, 1);
|
||||
} else {
|
||||
iterations = value;
|
||||
}
|
||||
|
||||
var done = new AtomicBoolean();
|
||||
var lock = new Object();
|
||||
|
||||
Runnable task = () -> {
|
||||
long count = 0L;
|
||||
while (!done.get()) {
|
||||
synchronized (lock) {
|
||||
pause();
|
||||
}
|
||||
count++;
|
||||
}
|
||||
System.out.format("%s %s => %d loops%n", Instant.now(), Thread.currentThread(), count);
|
||||
};
|
||||
|
||||
var thread1 = Thread.ofVirtual().start(task);
|
||||
var thread2 = Thread.ofVirtual().start(task);
|
||||
long lastTime = System.nanoTime();
|
||||
try {
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
thread1.getStackTrace();
|
||||
pause();
|
||||
thread2.getStackTrace();
|
||||
pause();
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
if (i == iterations || ((currentTime - lastTime) > 1_000_000_000L)) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
// fail quickly if interrupted by jtreg
|
||||
throw new RuntimeException("interrupted");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
done.set(true);
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
}
|
||||
|
||||
private static void pause() {
|
||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
Thread.onSpinWait();
|
||||
} else {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Stress test asynchronous Thread.getStackTrace when parking
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main GetStackTraceALotWhenParking
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/timeout=300 GetStackTraceALotWhenParking 1000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
public class GetStackTraceALotWhenParking {
|
||||
static class RoundRobinExecutor implements Executor, AutoCloseable {
|
||||
private final ExecutorService[] executors;
|
||||
private int next;
|
||||
|
||||
RoundRobinExecutor() {
|
||||
var factory = Thread.ofPlatform().name("worker-", 1).daemon(true).factory();
|
||||
var executors = new ExecutorService[2];
|
||||
for (int i = 0; i < executors.length; i++) {
|
||||
executors[i] = Executors.newSingleThreadExecutor(factory);
|
||||
}
|
||||
this.executors = executors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
executors[next].execute(task);
|
||||
next = (next + 1) % executors.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
for (int i = 0; i < executors.length; i++) {
|
||||
executors[i].shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 10_000;
|
||||
|
||||
final int ITERATIONS = iterations;
|
||||
final int SPIN_NANOS = 5000;
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
try (RoundRobinExecutor executor = new RoundRobinExecutor()) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(executor);
|
||||
|
||||
Thread thread = factory.newThread(() -> {
|
||||
while (count.incrementAndGet() < ITERATIONS) {
|
||||
long start = System.nanoTime();
|
||||
while ((System.nanoTime() - start) < SPIN_NANOS) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
LockSupport.parkNanos(500_000);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
long start = System.nanoTime();
|
||||
while (thread.isAlive()) {
|
||||
StackTraceElement[] stackTrace = thread.getStackTrace();
|
||||
// printStackTrace(stackTrace);
|
||||
Thread.sleep(5);
|
||||
if ((System.nanoTime() - start) > 500_000_000) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), count.get(), ITERATIONS);
|
||||
start = System.nanoTime();
|
||||
}
|
||||
}
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != ITERATIONS) {
|
||||
throw new RuntimeException("count = " + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void printStackTrace(StackTraceElement[] stackTrace) {
|
||||
if (stackTrace == null) {
|
||||
System.out.println("NULL");
|
||||
} else {
|
||||
for (var e : stackTrace) {
|
||||
System.out.println("\t" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8322818
|
||||
* @summary Stress test Thread.getStackTrace on a virtual thread that is pinned
|
||||
* @requires vm.debug != true
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 10000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 5000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.Platform;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class GetStackTraceALotWhenPinned {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// need at least two carrier threads when main thread is a virtual thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
int iterations;
|
||||
int value = Integer.parseInt(args[0]);
|
||||
if (Platform.isOSX()) {
|
||||
// reduced iterations on macosx
|
||||
iterations = Math.max(value / 4, 1);
|
||||
} else {
|
||||
iterations = value;
|
||||
}
|
||||
|
||||
var barrier = new Barrier(2);
|
||||
|
||||
// Start a virtual thread that loops doing Thread.yield and parking while pinned.
|
||||
// This loop creates the conditions for the main thread to sample the stack trace
|
||||
// as it transitions from being unmounted to parking while pinned.
|
||||
var thread = Thread.startVirtualThread(() -> {
|
||||
boolean timed = false;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
// wait for main thread to arrive
|
||||
barrier.await();
|
||||
|
||||
Thread.yield();
|
||||
boolean b = timed;
|
||||
VThreadPinner.runPinned(() -> {
|
||||
if (b) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
timed = !timed;
|
||||
}
|
||||
});
|
||||
|
||||
long lastTime = System.nanoTime();
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
// wait for virtual thread to arrive
|
||||
barrier.await();
|
||||
|
||||
thread.getStackTrace();
|
||||
LockSupport.unpark(thread);
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
if (i == iterations || ((currentTime - lastTime) > 1_000_000_000L)) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
// fail quickly if interrupted by jtreg
|
||||
throw new RuntimeException("interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alow threads wait for each other to reach a common barrier point. This class does
|
||||
* not park threads that are waiting for the barrier to trip, instead it spins. This
|
||||
* makes it suitable for tests that use LockSupport.park or Thread.yield.
|
||||
*/
|
||||
private static class Barrier {
|
||||
private final int parties;
|
||||
private final AtomicInteger count;
|
||||
private volatile int generation;
|
||||
|
||||
Barrier(int parties) {
|
||||
this.parties = parties;
|
||||
this.count = new AtomicInteger(parties);
|
||||
}
|
||||
|
||||
void await() {
|
||||
int g = generation;
|
||||
if (count.decrementAndGet() == 0) {
|
||||
count.set(parties);
|
||||
generation = g + 1;
|
||||
} else {
|
||||
while (generation == g) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.getStackTrace on a virtual thread in timed-Object.wait
|
||||
* @requires vm.debug != true
|
||||
* @run main/timeout=480 GetStackTraceALotWithTimedWait 100000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @run main/timeout=480 GetStackTraceALotWithTimedWait 50000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class GetStackTraceALotWithTimedWait {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 100_000;
|
||||
|
||||
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
var done = new AtomicBoolean();
|
||||
var threads = new ArrayList<Thread>();
|
||||
|
||||
// start threads that invoke Object.wait with a short timeout
|
||||
int nthreads = Runtime.getRuntime().availableProcessors();
|
||||
for (int i = 0; i < nthreads; i++) {
|
||||
var ref = new AtomicReference<Thread>();
|
||||
var lock = new Object();
|
||||
executor.submit(() -> {
|
||||
ref.set(Thread.currentThread());
|
||||
while (!done.get()) {
|
||||
synchronized (lock) {
|
||||
int delay = 1 + ThreadLocalRandom.current().nextInt(20);
|
||||
lock.wait(delay);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
Thread thread;
|
||||
while ((thread = ref.get()) == null) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// hammer on Thread.getStackTrace
|
||||
try {
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
if ((i % 1000) == 0) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + iterations);
|
||||
}
|
||||
for (Thread thread : threads) {
|
||||
thread.getStackTrace();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads entering a lot of monitors with contention
|
||||
* @library /test/lib
|
||||
* @run main/timeout=480 LotsOfContendedMonitorEnter
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class LotsOfContendedMonitorEnter {
|
||||
static int depth;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0) {
|
||||
depth = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
depth = 1024;
|
||||
}
|
||||
|
||||
var exRef = new AtomicReference<Throwable>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
testContendedEnter(depth);
|
||||
} catch (Exception e) {
|
||||
exRef.set(e);
|
||||
}
|
||||
});
|
||||
thread.join();
|
||||
assert exRef.get() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the monitor for a new object, racing with another virtual thread that
|
||||
* attempts to enter around the same time, then repeat to the given depth.
|
||||
*/
|
||||
private static void testContendedEnter(int depthRemaining) throws Exception {
|
||||
boolean doLog = depthRemaining % 10 == 0;
|
||||
if (depthRemaining > 0) {
|
||||
var lock = new Object();
|
||||
|
||||
// start thread to enter monitor for brief period, then enters again when signalled
|
||||
var started = new CountDownLatch(1);
|
||||
var signal = new CountDownLatch(1);
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
|
||||
// enter, may be contended
|
||||
synchronized (lock) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
|
||||
// wait to be signalled
|
||||
try {
|
||||
signal.await();
|
||||
} catch (InterruptedException e) { }
|
||||
|
||||
// enter again, this will block until the main thread releases
|
||||
synchronized (lock) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start
|
||||
started.await();
|
||||
|
||||
// enter, may be contended
|
||||
synchronized (lock) {
|
||||
// signal thread to enter monitor again, it should block
|
||||
signal.countDown();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
if (doLog) {
|
||||
System.out.println(Instant.now() + " => at depth: " + (depth - depthRemaining));
|
||||
}
|
||||
testContendedEnter(depthRemaining - 1);
|
||||
}
|
||||
} finally {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
if (doLog) {
|
||||
System.out.println(Instant.now() + " => returning from depth: " + (depth - depthRemaining));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assert state != Thread.State.TERMINATED : "Thread has terminated";
|
||||
Thread.yield();
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual thread entering (and reentering) a lot of monitors with no contention
|
||||
* @library /test/lib
|
||||
* @run main LotsOfUncontendedMonitorEnter
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class LotsOfUncontendedMonitorEnter {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int depth;
|
||||
if (args.length > 0) {
|
||||
depth = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
depth = 24; // 33554430 enters
|
||||
}
|
||||
VThreadRunner.run(() -> {
|
||||
testEnter(List.of(), depth);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the monitor for a new object, reenter a monitor that is already held, and
|
||||
* repeat to the given depth.
|
||||
*/
|
||||
private static void testEnter(List<Object> ownedMonitors, int depthRemaining) {
|
||||
if (depthRemaining > 0) {
|
||||
var lock = new Object();
|
||||
synchronized (lock) {
|
||||
// new list of owned monitors
|
||||
var monitors = concat(ownedMonitors, lock);
|
||||
testEnter(monitors, depthRemaining - 1);
|
||||
|
||||
// reenter a monitor that is already owned
|
||||
int index = ThreadLocalRandom.current().nextInt(monitors.size());
|
||||
var otherLock = monitors.get(index);
|
||||
|
||||
synchronized (otherLock) {
|
||||
testEnter(monitors, depthRemaining - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to a list, returning a new list.
|
||||
*/
|
||||
private static <T> List<T> concat(List<T> list, T object) {
|
||||
var newList = new ArrayList<>(list);
|
||||
newList.add(object);
|
||||
return newList;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8373120
|
||||
* @summary Stress test two consecutive timed Object.wait calls where only the first one is notified.
|
||||
* @run main/othervm -XX:CompileCommand=exclude,java.lang.VirtualThread::afterYield NotifiedThenTimedOutWait 1 100 100
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @run main/othervm -XX:CompileCommand=exclude,java.lang.VirtualThread::afterYield NotifiedThenTimedOutWait 2 100 100
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Phaser;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class NotifiedThenTimedOutWait {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int race = (args.length > 0) ? Integer.parseInt(args[0]) : 1;
|
||||
int nruns = (args.length > 1) ? Integer.parseInt(args[1]) : 100;
|
||||
int iterations = (args.length > 2) ? Integer.parseInt(args[2]) : 100;
|
||||
|
||||
for (int i = 1; i <= nruns; i++) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + nruns);
|
||||
switch (race) {
|
||||
case 1 -> race1(iterations);
|
||||
case 2 -> race2(iterations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Barrier in synchronized block.
|
||||
*/
|
||||
private static void race1(int iterations) throws InterruptedException {
|
||||
final int timeout = 1;
|
||||
var lock = new Object();
|
||||
var start = new Phaser(2);
|
||||
var end = new Phaser(2);
|
||||
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
for (int j = 0; j < iterations; j++) {
|
||||
synchronized (lock) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
lock.wait(timeout);
|
||||
lock.wait(timeout);
|
||||
}
|
||||
end.arriveAndAwaitAdvance();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
|
||||
? Thread.ofPlatform().factory() : Thread.ofVirtual().factory();
|
||||
var notifier = factory.newThread(() -> {
|
||||
for (int j = 0; j < iterations; j++) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
end.arriveAndAwaitAdvance();
|
||||
}
|
||||
});
|
||||
notifier.start();
|
||||
|
||||
vthread.join();
|
||||
notifier.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Barrier before synchronized block.
|
||||
*/
|
||||
private static void race2(int iterations) throws InterruptedException {
|
||||
final int timeout = 1;
|
||||
var lock = new Object();
|
||||
var start = new Phaser(2);
|
||||
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
synchronized (lock) {
|
||||
lock.wait(timeout);
|
||||
lock.wait(timeout);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
|
||||
? Thread.ofPlatform().factory() : Thread.ofVirtual().factory();
|
||||
var notifier = factory.newThread(() -> {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
notifier.start();
|
||||
|
||||
vthread.join();
|
||||
notifier.join();
|
||||
}
|
||||
}
|
||||
114
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
114
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test parking and unparking
|
||||
* @requires vm.debug != true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 ParkALot 300000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 ParkALot 100000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.Platform;
|
||||
|
||||
public class ParkALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
int value = Integer.parseInt(args[0]);
|
||||
if (Platform.isOSX()) {
|
||||
// reduced iterations on macosx
|
||||
iterations = Math.max(value / 4, 1);
|
||||
} else {
|
||||
iterations = value;
|
||||
}
|
||||
|
||||
int maxThreads = Math.clamp(Runtime.getRuntime().availableProcessors() / 2, 1, 4);
|
||||
for (int nthreads = 1; nthreads <= maxThreads; nthreads++) {
|
||||
System.out.format("%s %d thread(s) ...%n", Instant.now(), nthreads);
|
||||
ThreadFactory factory = Thread.ofPlatform().factory();
|
||||
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
|
||||
var totalIterations = new AtomicInteger();
|
||||
for (int i = 0; i < nthreads; i++) {
|
||||
executor.submit(() -> parkALot(iterations, totalIterations::incrementAndGet));
|
||||
}
|
||||
|
||||
// shutdown, await for all threads to finish with progress output
|
||||
executor.shutdown();
|
||||
boolean terminated = false;
|
||||
while (!terminated) {
|
||||
terminated = executor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
System.out.format("%s => %d of %d%n",
|
||||
Instant.now(), totalIterations.get(), iterations * nthreads);
|
||||
}
|
||||
}
|
||||
System.out.format("%s %d thread(s) done%n", Instant.now(), nthreads);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a virtual thread that alternates between untimed and timed parking.
|
||||
* A platform thread spins unparking the virtual thread.
|
||||
* @param iterations number of iterations
|
||||
* @param afterIteration the task to run after each iteration
|
||||
*/
|
||||
private static void parkALot(int iterations, Runnable afterIteration) {
|
||||
Thread vthread = Thread.ofVirtual().start(() -> {
|
||||
int i = 0;
|
||||
boolean timed = false;
|
||||
while (i < iterations) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
timed = false;
|
||||
} else {
|
||||
LockSupport.park();
|
||||
timed = true;
|
||||
}
|
||||
i++;
|
||||
afterIteration.run();
|
||||
}
|
||||
});
|
||||
|
||||
Thread.State state;
|
||||
while ((state = vthread.getState()) != Thread.State.TERMINATED) {
|
||||
if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
|
||||
LockSupport.unpark(vthread);
|
||||
} else {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java
Normal file
120
test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=parked
|
||||
* @bug 8369227
|
||||
* @summary Stress test untimed park after a timed park when a thread is unparked around the
|
||||
* same time that the timeout expires.
|
||||
* @library /test/lib
|
||||
* @run main/othervm --enable-native-access=ALL-UNNAMED ParkAfterTimedPark 200 false
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=pinned
|
||||
* @summary Stress test untimed park, while pinned, and after a timed park when a thread is
|
||||
* unparked around the same time that the timeout expires.
|
||||
* @library /test/lib
|
||||
* @run main/othervm --enable-native-access=ALL-UNNAMED ParkAfterTimedPark 200 true
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class ParkAfterTimedPark {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 100;
|
||||
boolean pinned = (args.length > 1) ? Boolean.parseBoolean(args[1]) : false;
|
||||
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + iterations);
|
||||
for (int timeout = 1; timeout <= 10; timeout++) {
|
||||
test(timeout, pinned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates two virtual threads. The first does a timed-park for the given time,
|
||||
* then parks in CountDownLatch.await. A second virtual thread unparks the first
|
||||
* around the same time that the timeout for the first expires.
|
||||
*/
|
||||
private static void test(int millis, boolean pinned) throws Exception {
|
||||
long nanos = TimeUnit.MILLISECONDS.toNanos(millis);
|
||||
|
||||
var finish = new CountDownLatch(1);
|
||||
|
||||
Thread thread1 = Thread.startVirtualThread(() -> {
|
||||
LockSupport.parkNanos(nanos);
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
finish.await();
|
||||
});
|
||||
} else {
|
||||
finish.await();
|
||||
}
|
||||
done = true;
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
|
||||
Thread thread2 = Thread.startVirtualThread(() -> {
|
||||
int delta = ThreadLocalRandom.current().nextInt(millis);
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
try {
|
||||
Thread.sleep(millis - delta);
|
||||
done = true;
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
LockSupport.unpark(thread1);
|
||||
});
|
||||
|
||||
// wait for first thread to park before count down
|
||||
await(thread1, Thread.State.WAITING);
|
||||
finish.countDown();
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) throws Exception {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
if (state == Thread.State.TERMINATED)
|
||||
throw new RuntimeException("Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
76
test/jdk/java/lang/Thread/virtual/stress/PinALot.java
Normal file
76
test/jdk/java/lang/Thread/virtual/stress/PinALot.java
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test timed park when pinned
|
||||
* @requires vm.debug != true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED PinALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=1200 --enable-native-access=ALL-UNNAMED PinALot 200000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class PinALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
LockSupport.parkNanos(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
test/jdk/java/lang/Thread/virtual/stress/PingPong.java
Normal file
131
test/jdk/java/lang/Thread/virtual/stress/PingPong.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=sq
|
||||
* @summary Stress test virtual threads with a SynchronousQueue
|
||||
* @requires vm.debug != true
|
||||
* @run main PingPong SQ 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=ltq
|
||||
* @summary Stress test virtual threads with a LinkedTransferQueue
|
||||
* @requires vm.debug != true
|
||||
* @run main PingPong LTQ 500000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PingPong {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Exchanger<String> queue;
|
||||
int iterations;
|
||||
if (args.length == 0) {
|
||||
queue = new LTQExchanger<>();
|
||||
iterations = 10_000_000;
|
||||
} else {
|
||||
if (args[0].equals("SQ")) {
|
||||
queue = new SQExchanger<>();
|
||||
} else {
|
||||
queue = new LTQExchanger<>();
|
||||
}
|
||||
iterations = Integer.parseInt(args[1]);
|
||||
}
|
||||
|
||||
AtomicInteger count1 = new AtomicInteger();
|
||||
AtomicInteger count2 = new AtomicInteger();
|
||||
|
||||
Thread t1 = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
while (count1.incrementAndGet() < iterations) {
|
||||
queue.transfer("hello");
|
||||
String reply = queue.take();
|
||||
if (!"ack".equals(reply)) {
|
||||
throw new RuntimeException("reply=" + reply);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
Thread t2 = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
while (count2.incrementAndGet() < iterations) {
|
||||
String message = queue.take();
|
||||
if (!"hello".equals(message)) {
|
||||
throw new RuntimeException("message=" + message);
|
||||
}
|
||||
queue.transfer("ack");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = t1.join(Duration.ofMillis(500));
|
||||
if (terminated) {
|
||||
terminated = t2.join(Duration.ofMillis(500));
|
||||
}
|
||||
System.out.format("%s => T1 %d of %d, T2 %d of %d%n",
|
||||
Instant.now(), count1.get(), iterations, count2.get(), iterations);
|
||||
} while (!terminated);
|
||||
}
|
||||
|
||||
interface Exchanger<E> {
|
||||
void transfer (E value) throws InterruptedException;
|
||||
E take() throws InterruptedException;
|
||||
}
|
||||
|
||||
static class SQExchanger<E> implements Exchanger<E> {
|
||||
private final SynchronousQueue<E> queue = new SynchronousQueue<>();
|
||||
@Override
|
||||
public void transfer(E value) throws InterruptedException {
|
||||
queue.put(value);
|
||||
}
|
||||
@Override
|
||||
public E take() throws InterruptedException {
|
||||
return queue.take();
|
||||
}
|
||||
}
|
||||
|
||||
static class LTQExchanger<E> implements Exchanger<E> {
|
||||
private final LinkedTransferQueue<E> queue = new LinkedTransferQueue<>();
|
||||
@Override
|
||||
public void transfer(E value) throws InterruptedException {
|
||||
queue.transfer(value);
|
||||
}
|
||||
@Override
|
||||
public E take() throws InterruptedException {
|
||||
return queue.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
119
test/jdk/java/lang/Thread/virtual/stress/Skynet.java
Normal file
119
test/jdk/java/lang/Thread/virtual/stress/Skynet.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @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=1600 -Xmx1500m Skynet
|
||||
*/
|
||||
/*
|
||||
* @test id=Z
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @requires vm.gc.Z
|
||||
* @run main/othervm/timeout=1600 -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+UseZGC
|
||||
* -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1500m Skynet
|
||||
*/
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class Skynet {
|
||||
public static void main(String[] args) {
|
||||
int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 10;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
skynet(1_000_000, 499999500000L);
|
||||
}
|
||||
}
|
||||
|
||||
static void skynet(int num, long expected) {
|
||||
long start = System.currentTimeMillis();
|
||||
var chan = new Channel<Long>();
|
||||
|
||||
Thread.startVirtualThread(() -> skynet(chan, 0, num, 10));
|
||||
|
||||
long sum = chan.receive();
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.format("Result: %d in %s ms%n", sum, (end-start));
|
||||
if (sum != expected)
|
||||
throw new RuntimeException("Expected " + expected);
|
||||
}
|
||||
|
||||
static void skynet(Channel<Long> result, int num, int size, int div) {
|
||||
if (size == 1) {
|
||||
result.send((long)num);
|
||||
} else {
|
||||
var chan = new Channel<Long>();
|
||||
for (int i = 0; i < div; i++) {
|
||||
int subNum = num + i * (size / div);
|
||||
Thread.startVirtualThread(() -> skynet(chan, subNum, size / div, div));
|
||||
}
|
||||
long sum = 0;
|
||||
for (int i = 0; i < div; i++) {
|
||||
sum += chan.receive();
|
||||
}
|
||||
result.send(sum);
|
||||
}
|
||||
}
|
||||
|
||||
static class Channel<T> {
|
||||
private final BlockingQueue<T> q;
|
||||
|
||||
Channel() {
|
||||
q = new SynchronousQueue<>();
|
||||
}
|
||||
|
||||
void send(T e) {
|
||||
boolean interrupted = false;
|
||||
while (true) {
|
||||
try {
|
||||
q.put(e);
|
||||
break;
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
T receive() {
|
||||
boolean interrupted = false;
|
||||
T e;
|
||||
while (true) {
|
||||
try {
|
||||
e = q.take();
|
||||
break;
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test virtual threads with a variation of the Skynet 1M benchmark that uses
|
||||
* a channel implementation based on object monitors. This variant uses a reduced number of
|
||||
* 100k virtual threads at the final level.
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/othervm/timeout=1200 Skynet100kWithMonitors 50
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @run main/othervm/timeout=1200 Skynet100kWithMonitors 10
|
||||
*/
|
||||
|
||||
public class Skynet100kWithMonitors {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int iterations = (args.length) > 0 ? Integer.parseInt(args[0]) : 10;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
skynet(100_000, 4999950000L);
|
||||
}
|
||||
}
|
||||
|
||||
static void skynet(int num, long expected) {
|
||||
long start = System.currentTimeMillis();
|
||||
var chan = new Channel<Long>();
|
||||
|
||||
Thread.startVirtualThread(() -> skynet(chan, 0, num, 10));
|
||||
|
||||
long sum = chan.receive();
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.format("Result: %d in %s ms%n", sum, (end-start));
|
||||
if (sum != expected)
|
||||
throw new RuntimeException("Expected " + expected);
|
||||
}
|
||||
|
||||
static void skynet(Channel<Long> result, int num, int size, int div) {
|
||||
if (size == 1) {
|
||||
result.send((long)num);
|
||||
} else {
|
||||
var chan = new Channel<Long>();
|
||||
for (int i = 0; i < div; i++) {
|
||||
int subNum = num + i * (size / div);
|
||||
Thread.startVirtualThread(() -> skynet(chan, subNum, size / div, div));
|
||||
}
|
||||
long sum = 0;
|
||||
for (int i = 0; i < div; i++) {
|
||||
sum += chan.receive();
|
||||
}
|
||||
result.send(sum);
|
||||
}
|
||||
}
|
||||
|
||||
static class Channel<T> {
|
||||
private final Object lock = new Object();
|
||||
private T element;
|
||||
|
||||
Channel() {
|
||||
}
|
||||
|
||||
void send(T e) {
|
||||
boolean interrupted = false;
|
||||
synchronized (lock) {
|
||||
while (element != null) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
element = e;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
T receive() {
|
||||
T e;
|
||||
boolean interrupted = false;
|
||||
synchronized (lock) {
|
||||
while ((e = element) == null) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
element = null;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
test/jdk/java/lang/Thread/virtual/stress/SleepALot.java
Normal file
71
test/jdk/java/lang/Thread/virtual/stress/SleepALot.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.sleep
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/timeout=480 SleepALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @run main/othervm/timeout=1200 SleepALot 200000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class SleepALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
try {
|
||||
Thread.sleep(Duration.ofNanos(100));
|
||||
} catch (InterruptedException ignore) { }
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java
Normal file
132
test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout
|
||||
* @summary Stress test timed-Object.wait
|
||||
* @run main TimedWaitALot 200
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout-notify
|
||||
* @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
|
||||
* at around the same time that the timeout expires.
|
||||
* @run main TimedWaitALot 150 true false
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout-interrupt
|
||||
* @summary Test timed-Object.wait where the waiting thread is awakened with Thread.interrupt
|
||||
* at around the same time that the timeout expires.
|
||||
* @run main TimedWaitALot 150 false true
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout-notify-interrupt
|
||||
* @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
|
||||
* and Thread.interrupt at around the same time that the timeout expires.
|
||||
* @run main TimedWaitALot 100 true true
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class TimedWaitALot {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = Integer.parseInt(args[0]);
|
||||
boolean notify = args.length >= 2 && "true".equals(args[1]);
|
||||
boolean interrupt = args.length >=3 && "true".equals(args[2]);
|
||||
|
||||
// test all timeouts concurrently
|
||||
int[] timeouts = { 10, 20, 50, 100 };
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + iterations);
|
||||
test(notify, interrupt, timeouts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a first virtual thread to wait in Object.wait(millis).
|
||||
* If {@code notify} is true, start a virtual thread to use Object.notifyAll at around
|
||||
* the same time that the timeout expires.
|
||||
* If {@code interrupt} is true, start virtual thread to interrupts the first virtual
|
||||
* thread at around the same time as the timeout expires.
|
||||
*/
|
||||
static void test(boolean notify, boolean interrupt, int... timeouts) throws Exception {
|
||||
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
for (int timeout : timeouts) {
|
||||
var queue = new SynchronousQueue<Thread>();
|
||||
var lock = new Object();
|
||||
|
||||
// virtual thread waits with Object.wait(timeout)
|
||||
executor.submit(() -> {
|
||||
queue.put(Thread.currentThread());
|
||||
synchronized (lock) {
|
||||
lock.wait(timeout);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// wait for thread to start
|
||||
Thread thread = queue.take();
|
||||
|
||||
// start thread to Object.notifyAll at around time that the timeout expires
|
||||
if (notify) {
|
||||
executor.submit(() -> {
|
||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
synchronized (lock) {
|
||||
sleepLessThan(timeout);
|
||||
lock.notifyAll();
|
||||
}
|
||||
} else {
|
||||
sleepLessThan(timeout);
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// start thread to interrupt first thread at around time that the timeout expires
|
||||
if (interrupt) {
|
||||
executor.submit(() -> {
|
||||
sleepLessThan(timeout);
|
||||
thread.interrupt();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for just less than the given timeout, in millis.
|
||||
*/
|
||||
private static void sleepLessThan(long timeout) throws InterruptedException {
|
||||
int delta = ThreadLocalRandom.current().nextInt(10);
|
||||
Thread.sleep(timeout - delta);
|
||||
}
|
||||
}
|
||||
69
test/jdk/java/lang/Thread/virtual/stress/YieldALot.java
Normal file
69
test/jdk/java/lang/Thread/virtual/stress/YieldALot.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.yield
|
||||
* @requires vm.debug != true
|
||||
* @run main YieldALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @run main YieldALot 200000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class YieldALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
Thread.yield();
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
test/jdk/java/lang/Thread/virtual/stress/skynet.md
Normal file
25
test/jdk/java/lang/Thread/virtual/stress/skynet.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
## Skynet f341a13
|
||||
|
||||
### MIT License
|
||||
|
||||
```
|
||||
Copyright (c) 2022 Alexander Temerev and contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue