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:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,805 @@
/*
* 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 Unit test for Thread.Builder
* @run junit BuilderTest
*/
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;
class BuilderTest {
/**
* Test Thread.ofPlatform to create platform threads.
*/
@Test
void testPlatformThread() throws Exception {
Thread parent = Thread.currentThread();
Thread.Builder.OfPlatform builder = Thread.ofPlatform();
// unstarted
AtomicBoolean done1 = new AtomicBoolean();
Thread thread1 = builder.unstarted(() -> done1.set(true));
assertFalse(thread1.isVirtual());
assertTrue(thread1.getState() == Thread.State.NEW);
assertFalse(thread1.getName().isEmpty());
assertTrue(thread1.getThreadGroup() == parent.getThreadGroup());
assertTrue(thread1.isDaemon() == parent.isDaemon());
assertTrue(thread1.getPriority() == parent.getPriority());
assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
thread1.start();
thread1.join();
assertTrue(done1.get());
// start
AtomicBoolean done2 = new AtomicBoolean();
Thread thread2 = builder.start(() -> done2.set(true));
assertFalse(thread2.isVirtual());
assertTrue(thread2.getState() != Thread.State.NEW);
assertFalse(thread2.getName().isEmpty());
ThreadGroup group2 = thread2.getThreadGroup();
assertTrue(group2 == parent.getThreadGroup() || group2 == null);
assertTrue(thread2.isDaemon() == parent.isDaemon());
assertTrue(thread2.getPriority() == parent.getPriority());
assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
thread2.join();
assertTrue(done2.get());
// factory
AtomicBoolean done3 = new AtomicBoolean();
Thread thread3 = builder.factory().newThread(() -> done3.set(true));
assertFalse(thread3.isVirtual());
assertTrue(thread3.getState() == Thread.State.NEW);
assertFalse(thread3.getName().isEmpty());
assertTrue(thread3.getThreadGroup() == parent.getThreadGroup());
assertTrue(thread3.isDaemon() == parent.isDaemon());
assertTrue(thread3.getPriority() == parent.getPriority());
assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
thread3.start();
thread3.join();
assertTrue(done3.get());
}
/**
* Test Thread.ofVirtual to create virtual threads.
*/
@Test
void testVirtualThread() throws Exception {
Thread parent = Thread.currentThread();
Thread.Builder.OfVirtual builder = Thread.ofVirtual();
// unstarted
AtomicBoolean done1 = new AtomicBoolean();
Thread thread1 = builder.unstarted(() -> done1.set(true));
assertTrue(thread1.isVirtual());
assertTrue(thread1.getState() == Thread.State.NEW);
assertTrue(thread1.getName().isEmpty());
assertTrue(thread1.getContextClassLoader() == parent.getContextClassLoader());
assertTrue(thread1.isDaemon());
assertTrue(thread1.getPriority() == Thread.NORM_PRIORITY);
thread1.start();
thread1.join();
assertTrue(done1.get());
// start
AtomicBoolean done2 = new AtomicBoolean();
Thread thread2 = builder.start(() -> done2.set(true));
assertTrue(thread2.isVirtual());
assertTrue(thread2.getState() != Thread.State.NEW);
assertTrue(thread2.getName().isEmpty());
assertTrue(thread2.getContextClassLoader() == parent.getContextClassLoader());
assertTrue(thread2.isDaemon());
assertTrue(thread2.getPriority() == Thread.NORM_PRIORITY);
thread2.join();
assertTrue(done2.get());
// factory
AtomicBoolean done3 = new AtomicBoolean();
Thread thread3 = builder.factory().newThread(() -> done3.set(true));
assertTrue(thread3.isVirtual());
assertTrue(thread3.getState() == Thread.State.NEW);
assertTrue(thread3.getName().isEmpty());
assertTrue(thread3.getContextClassLoader() == parent.getContextClassLoader());
assertTrue(thread3.isDaemon());
assertTrue(thread3.getPriority() == Thread.NORM_PRIORITY);
thread3.start();
thread3.join();
assertTrue(done3.get());
}
/**
* Test Thread.Builder.name.
*/
@Test
void testName1() {
Thread.Builder builder = Thread.ofPlatform().name("duke");
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.getName().equals("duke"));
assertTrue(thread2.getName().equals("duke"));
assertTrue(thread3.getName().equals("duke"));
}
@Test
void testName2() {
Thread.Builder builder = Thread.ofVirtual().name("duke");
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.getName().equals("duke"));
assertTrue(thread2.getName().equals("duke"));
assertTrue(thread3.getName().equals("duke"));
}
@Test
void testName3() {
Thread.Builder builder = Thread.ofPlatform().name("duke-", 100);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.unstarted(() -> { });
Thread thread3 = builder.unstarted(() -> { });
assertTrue(thread1.getName().equals("duke-100"));
assertTrue(thread2.getName().equals("duke-101"));
assertTrue(thread3.getName().equals("duke-102"));
ThreadFactory factory = builder.factory();
Thread thread4 = factory.newThread(() -> { });
Thread thread5 = factory.newThread(() -> { });
Thread thread6 = factory.newThread(() -> { });
assertTrue(thread4.getName().equals("duke-103"));
assertTrue(thread5.getName().equals("duke-104"));
assertTrue(thread6.getName().equals("duke-105"));
}
@Test
void testName4() {
Thread.Builder builder = Thread.ofVirtual().name("duke-", 100);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.unstarted(() -> { });
Thread thread3 = builder.unstarted(() -> { });
assertTrue(thread1.getName().equals("duke-100"));
assertTrue(thread2.getName().equals("duke-101"));
assertTrue(thread3.getName().equals("duke-102"));
ThreadFactory factory = builder.factory();
Thread thread4 = factory.newThread(() -> { });
Thread thread5 = factory.newThread(() -> { });
Thread thread6 = factory.newThread(() -> { });
assertTrue(thread4.getName().equals("duke-103"));
assertTrue(thread5.getName().equals("duke-104"));
assertTrue(thread6.getName().equals("duke-105"));
}
/**
* Test Thread.Builder.OfPlatform.group.
*/
@Test
void testThreadGroup1() {
ThreadGroup group = new ThreadGroup("groupies");
Thread.Builder builder = Thread.ofPlatform().group(group);
Thread thread1 = builder.unstarted(() -> { });
AtomicBoolean done = new AtomicBoolean();
Thread thread2 = builder.start(() -> {
while (!done.get()) {
LockSupport.park();
}
});
Thread thread3 = builder.factory().newThread(() -> { });
try {
assertTrue(thread1.getThreadGroup() == group);
assertTrue(thread2.getThreadGroup() == group);
assertTrue(thread3.getThreadGroup() == group);
} finally {
done.set(true);
LockSupport.unpark(thread2);
}
}
@Test
void testThreadGroup2() {
ThreadGroup vgroup = Thread.ofVirtual().unstarted(() -> { }).getThreadGroup();
assertEquals("VirtualThreads", vgroup.getName());
Thread thread1 = Thread.ofVirtual().unstarted(() -> { });
Thread thread2 = Thread.ofVirtual().start(LockSupport::park);
Thread thread3 = Thread.ofVirtual().factory().newThread(() -> { });
try {
assertTrue(thread1.getThreadGroup() == vgroup);
assertTrue(thread2.getThreadGroup() == vgroup);
assertTrue(thread3.getThreadGroup() == vgroup);
} finally {
LockSupport.unpark(thread2);
}
}
/**
* Test Thread.Builder.OfPlatform.priority.
*/
@Test
void testPriority1() {
int priority = Thread.currentThread().getPriority();
Thread.Builder builder = Thread.ofPlatform();
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.getPriority() == priority);
assertTrue(thread2.getPriority() == priority);
assertTrue(thread3.getPriority() == priority);
}
@Test
void testPriority2() {
int priority = Thread.MIN_PRIORITY;
Thread.Builder builder = Thread.ofPlatform().priority(priority);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.getPriority() == priority);
assertTrue(thread2.getPriority() == priority);
assertTrue(thread3.getPriority() == priority);
}
@Test
void testPriority3() {
Thread currentThread = Thread.currentThread();
assumeFalse(currentThread.isVirtual(), "Main thread is a virtual thread");
int maxPriority = currentThread.getThreadGroup().getMaxPriority();
int priority = Math.min(maxPriority + 1, Thread.MAX_PRIORITY);
Thread.Builder builder = Thread.ofPlatform().priority(priority);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.getPriority() == priority);
assertTrue(thread2.getPriority() == priority);
assertTrue(thread3.getPriority() == priority);
}
@Test
void testPriority4() {
var builder = Thread.ofPlatform();
assertThrows(IllegalArgumentException.class,
() -> builder.priority(Thread.MIN_PRIORITY - 1));
}
@Test
void testPriority5() {
var builder = Thread.ofPlatform();
assertThrows(IllegalArgumentException.class,
() -> builder.priority(Thread.MAX_PRIORITY + 1));
}
/**
* Test Thread.Builder.OfPlatform.daemon.
*/
@Test
void testDaemon1() {
Thread.Builder builder = Thread.ofPlatform().daemon(false);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertFalse(thread1.isDaemon());
assertFalse(thread2.isDaemon());
assertFalse(thread3.isDaemon());
}
@Test
void testDaemon2() {
Thread.Builder builder = Thread.ofPlatform().daemon(true);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.isDaemon());
assertTrue(thread2.isDaemon());
assertTrue(thread3.isDaemon());
}
@Test
void testDaemon3() {
Thread.Builder builder = Thread.ofPlatform().daemon();
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
assertTrue(thread1.isDaemon());
assertTrue(thread2.isDaemon());
assertTrue(thread3.isDaemon());
}
@Test
void testDaemon4() {
Thread.Builder builder = Thread.ofPlatform();
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
// daemon status should be inherited
boolean d = Thread.currentThread().isDaemon();
assertTrue(thread1.isDaemon() == d);
assertTrue(thread2.isDaemon() == d);
assertTrue(thread3.isDaemon() == d);
}
/**
* Test Thread.ofVirtual creates daemon threads.
*/
@Test
void testDaemon5() {
Thread.Builder builder = Thread.ofVirtual();
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
// daemon status should always be true
assertTrue(thread1.isDaemon());
assertTrue(thread2.isDaemon());
assertTrue(thread3.isDaemon());
}
/**
* Test Thread.Builder.OfPlatform.stackSize.
*/
@Test
void testStackSize1() {
Thread.Builder builder = Thread.ofPlatform().stackSize(1024*1024);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
}
@Test
void testStackSize2() {
Thread.Builder builder = Thread.ofPlatform().stackSize(0);
Thread thread1 = builder.unstarted(() -> { });
Thread thread2 = builder.start(() -> { });
Thread thread3 = builder.factory().newThread(() -> { });
}
@Test
void testStackSize3() {
var builder = Thread.ofPlatform();
assertThrows(IllegalArgumentException.class, () -> builder.stackSize(-1));
}
/**
* Test Thread.Builder.uncaughtExceptionHandler.
*/
@Test
void testUncaughtExceptionHandler1() throws Exception {
class FooException extends RuntimeException { }
AtomicReference<Thread> threadRef = new AtomicReference<>();
AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
Thread thread = Thread.ofPlatform()
.uncaughtExceptionHandler((t, e) -> {
assertTrue(t == Thread.currentThread());
threadRef.set(t);
exceptionRef.set(e);
})
.start(() -> { throw new FooException(); });
thread.join();
assertTrue(threadRef.get() == thread);
assertTrue(exceptionRef.get() instanceof FooException);
}
@Test
void testUncaughtExceptionHandler2() throws Exception {
class FooException extends RuntimeException { }
AtomicReference<Thread> threadRef = new AtomicReference<>();
AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
Thread thread = Thread.ofVirtual()
.uncaughtExceptionHandler((t, e) -> {
assertTrue(t == Thread.currentThread());
threadRef.set(t);
exceptionRef.set(e);
})
.start(() -> { throw new FooException(); });
thread.join();
assertTrue(threadRef.get() == thread);
assertTrue(exceptionRef.get() instanceof FooException);
}
@Test
void testUncaughtExceptionHandler3() throws Exception {
class FooException extends RuntimeException { }
AtomicReference<Thread> threadRef = new AtomicReference<>();
AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
Thread thread = Thread.ofPlatform()
.uncaughtExceptionHandler((t, e) -> {
assertTrue(t == Thread.currentThread());
threadRef.set(t);
exceptionRef.set(e);
})
.factory()
.newThread(() -> { throw new FooException(); });
thread.start();
thread.join();
assertTrue(threadRef.get() == thread);
assertTrue(exceptionRef.get() instanceof FooException);
}
@Test
void testUncaughtExceptionHandler4() throws Exception {
class FooException extends RuntimeException { }
AtomicReference<Thread> threadRef = new AtomicReference<>();
AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
Thread thread = Thread.ofPlatform()
.uncaughtExceptionHandler((t, e) -> {
assertTrue(t == Thread.currentThread());
threadRef.set(t);
exceptionRef.set(e);
})
.factory()
.newThread(() -> { throw new FooException(); });
thread.start();
thread.join();
assertTrue(threadRef.get() == thread);
assertTrue(exceptionRef.get() instanceof FooException);
}
static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();
/**
* Tests that a builder creates threads that support thread locals
*/
private void testThreadLocals(Thread.Builder builder) throws Exception {
AtomicBoolean done = new AtomicBoolean();
Runnable task = () -> {
Object value = new Object();
LOCAL.set(value);
assertTrue(LOCAL.get() == value);
done.set(true);
};
done.set(false);
Thread thread1 = builder.unstarted(task);
thread1.start();
thread1.join();
assertTrue(done.get());
done.set(false);
Thread thread2 = builder.start(task);
thread2.join();
assertTrue(done.get());
done.set(false);
Thread thread3 = builder.factory().newThread(task);
thread3.start();
thread3.join();
assertTrue(done.get());
}
/**
* Tests that a builder creates threads that do not support thread locals
*/
private void testNoThreadLocals(Thread.Builder builder) throws Exception {
AtomicBoolean done = new AtomicBoolean();
Runnable task = () -> {
try {
LOCAL.set(new Object());
} catch (UnsupportedOperationException expected) {
done.set(true);
}
};
done.set(false);
Thread thread1 = builder.unstarted(task);
thread1.start();
thread1.join();
assertTrue(done.get());
done.set(false);
Thread thread2 = builder.start(task);
thread2.join();
assertTrue(done.get());
done.set(false);
Thread thread3 = builder.factory().newThread(task);
thread3.start();
thread3.join();
assertTrue(done.get());
}
/**
* Test Thread.Builder creates threads that allow thread locals.
*/
@Test
void testThreadLocals1() throws Exception {
Thread.Builder builder = Thread.ofPlatform();
testThreadLocals(builder);
}
@Test
void testThreadLocals2() throws Exception {
Thread.Builder builder = Thread.ofVirtual();
testThreadLocals(builder);
}
/**
* Tests that a builder creates threads that inherits the initial values of
* inheritable thread locals.
*/
private void testInheritedThreadLocals(Thread.Builder builder) throws Exception {
Object value = new Object();
INHERITED_LOCAL.set(value);
AtomicBoolean done = new AtomicBoolean();
Runnable task = () -> {
assertTrue(INHERITED_LOCAL.get() == value);
done.set(true);
};
done.set(false);
Thread thread1 = builder.unstarted(task);
thread1.start();
thread1.join();
assertTrue(done.get());
done.set(false);
Thread thread2 = builder.start(task);
thread2.join();
assertTrue(done.get());
done.set(false);
Thread thread3 = builder.factory().newThread(task);
thread3.start();
thread3.join();
assertTrue(done.get());
}
/**
* Tests that a builder creates threads that do not inherit the initial values
* of inheritable thread locals.
*/
private void testNoInheritedThreadLocals(Thread.Builder builder) throws Exception {
Object value = new Object();
INHERITED_LOCAL.set(value);
AtomicBoolean done = new AtomicBoolean();
Runnable task = () -> {
assertNull(INHERITED_LOCAL.get());
done.set(true);
};
done.set(false);
Thread thread1 = builder.unstarted(task);
thread1.start();
thread1.join();
assertTrue(done.get());
done.set(false);
Thread thread2 = builder.start(task);
thread2.join();
assertTrue(done.get());
done.set(false);
Thread thread3 = builder.factory().newThread(task);
thread3.start();
thread3.join();
assertTrue(done.get());
}
/**
* Test Thread.Builder creating threads that inherit or do not inherit
* the initial values of inheritable thread locals.
*/
@Test
void testInheritedThreadLocals1() throws Exception {
Thread.Builder builder = Thread.ofPlatform();
testInheritedThreadLocals(builder); // default
// do no inherit
builder.inheritInheritableThreadLocals(false);
testNoInheritedThreadLocals(builder);
// inherit
builder.inheritInheritableThreadLocals(true);
testInheritedThreadLocals(builder);
}
@Test
void testInheritedThreadLocals2() throws Exception {
Thread.Builder builder = Thread.ofVirtual();
testInheritedThreadLocals(builder); // default
// do no inherit
builder.inheritInheritableThreadLocals(false);
testNoInheritedThreadLocals(builder);
// inherit
builder.inheritInheritableThreadLocals(true);
testInheritedThreadLocals(builder);
}
/**
* Tests a builder creates threads that inherit the context class loader.
*/
private void testInheritContextClassLoader(Thread.Builder builder) throws Exception {
ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
try {
ClassLoader loader = new ClassLoader() { };
Thread.currentThread().setContextClassLoader(loader);
var ref = new AtomicReference<ClassLoader>();
Runnable task = () -> {
ref.set(Thread.currentThread().getContextClassLoader());
};
// unstarted
Thread thread1 = builder.unstarted(task);
assertTrue(thread1.getContextClassLoader() == loader);
// started
ref.set(null);
thread1.start();
thread1.join();
assertTrue(ref.get() == loader);
// factory
Thread thread2 = builder.factory().newThread(task);
assertTrue(thread2.getContextClassLoader() == loader);
// started
ref.set(null);
thread2.start();
thread2.join();
assertTrue(ref.get() == loader);
} finally {
Thread.currentThread().setContextClassLoader(savedCCL);
}
}
/**
* Tests a builder creates threads does not inherit the context class loader.
*/
private void testNoInheritContextClassLoader(Thread.Builder builder) throws Exception {
ClassLoader savedCCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader( new ClassLoader() { });
ClassLoader scl = ClassLoader.getSystemClassLoader();
var ref = new AtomicReference<ClassLoader>();
Runnable task = () -> {
ref.set(Thread.currentThread().getContextClassLoader());
};
// unstarted
Thread thread1 = builder.unstarted(task);
assertTrue(thread1.getContextClassLoader() == scl);
// started
ref.set(null);
thread1.start();
thread1.join();
assertTrue(ref.get() == scl);
// factory
Thread thread2 = builder.factory().newThread(task);
assertTrue(thread2.getContextClassLoader() == scl);
// started
ref.set(null);
thread2.start();
thread2.join();
assertTrue(ref.get() == scl);
} finally {
Thread.currentThread().setContextClassLoader(savedCCL);
}
}
/**
* Test Thread.Builder creating threads that inherit or do not inherit
* the thread context class loader.
*/
@Test
void testContextClassLoader1() throws Exception {
Thread.Builder builder = Thread.ofPlatform();
testInheritContextClassLoader(builder); // default
// do no inherit
builder.inheritInheritableThreadLocals(false);
testNoInheritContextClassLoader(builder);
// inherit
builder.inheritInheritableThreadLocals(true);
testInheritContextClassLoader(builder);
}
@Test
void testContextClassLoader2() throws Exception {
Thread.Builder builder = Thread.ofVirtual();
testInheritContextClassLoader(builder); // default
// do no inherit
builder.inheritInheritableThreadLocals(false);
testNoInheritContextClassLoader(builder);
// inherit
builder.inheritInheritableThreadLocals(true);
testInheritContextClassLoader(builder);
}
/**
* Test NullPointerException.
*/
@Test
void testNulls1() {
Thread.Builder.OfPlatform builder = Thread.ofPlatform();
assertThrows(NullPointerException.class, () -> builder.group(null));
assertThrows(NullPointerException.class, () -> builder.name(null));
assertThrows(NullPointerException.class, () -> builder.name(null, 0));
assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
assertThrows(NullPointerException.class, () -> builder.unstarted(null));
assertThrows(NullPointerException.class, () -> builder.start(null));
}
@Test
void testNulls2() {
Thread.Builder builder = Thread.ofVirtual();
assertThrows(NullPointerException.class, () -> builder.name(null));
assertThrows(NullPointerException.class, () -> builder.name(null, 0));
assertThrows(NullPointerException.class, () -> builder.uncaughtExceptionHandler(null));
assertThrows(NullPointerException.class, () -> builder.unstarted(null));
assertThrows(NullPointerException.class, () -> builder.start(null));
}
}

View file

@ -0,0 +1,189 @@
/*
* Copyright (c) 2004, 2013, 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 4919105 8004177
* @summary Generified basic unit test of Thread.getAllStackTraces()
* @author Mandy Chung
*/
import java.util.*;
public class GenerifyStackTraces {
private static Object go = new Object();
private static String[] methodNames = {"run", "A", "B", "C", "Done"};
private static int DONE_DEPTH = 5;
private static boolean testFailed = false;
private static Thread one;
private static boolean trace = false;
public static void main(String[] args) throws Exception {
if (args.length > 0 && args[0].equals("trace")) {
trace = true;
}
one = new ThreadOne();
one.start();
DumpThread dt = new DumpThread();
dt.start();
try {
one.join();
} finally {
dt.shutdown();
}
if (testFailed) {
throw new RuntimeException("Test Failed.");
}
}
static class DumpThread extends Thread {
private volatile boolean finished = false;
public void run() {
int depth = 2;
while (!finished) {
// At each iterator, wait until ThreadOne blocks
// to wait for thread dump.
// Then dump stack trace and notify ThreadOne to continue.
try {
sleep(2000);
dumpStacks(depth);
depth++;
finishDump();
} catch (Exception e) {
e.printStackTrace();
testFailed = true;
}
}
}
public void shutdown() throws InterruptedException {
finished = true;
this.join();
}
}
static class ThreadOne extends Thread {
public void run() {
A();
}
private void A() {
waitForDump();
B();
}
private void B() {
waitForDump();
C();
}
private void C() {
waitForDump();
Done();
}
private void Done() {
waitForDump();
// Get stack trace of current thread
StackTraceElement[] stack = getStackTrace();
try {
checkStack(this, stack, DONE_DEPTH);
} catch (Exception e) {
e.printStackTrace();
testFailed = true;
}
}
}
private static void waitForDump() {
synchronized(go) {
try {
go.wait();
} catch (Exception e) {
throw new RuntimeException("Unexpected exception" + e);
}
}
}
private static void finishDump() {
synchronized(go) {
try {
go.notifyAll();
} catch (Exception e) {
throw new RuntimeException("Unexpected exception" + e);
}
}
}
public static void dumpStacks(int depth) throws Exception {
// Get stack trace of another thread
StackTraceElement[] stack = one.getStackTrace();
checkStack(one, stack, depth);
// Get stack traces of all Threads
for (Map.Entry<Thread, StackTraceElement[]> entry :
Thread.getAllStackTraces().entrySet()) {
Thread t = entry.getKey();
stack = entry.getValue();
if (t == null || stack == null) {
throw new RuntimeException("Null thread or stacktrace returned");
}
if (t == one) {
checkStack(t, stack, depth);
}
}
}
private static void checkStack(Thread t, StackTraceElement[] stack,
int depth) throws Exception {
if (trace) {
printStack(t, stack);
}
int frame = stack.length - 1;
for (int i = 0; i < depth && frame >= 0; i++) {
if (! stack[frame].getMethodName().equals(methodNames[i])) {
throw new RuntimeException("Expected " + methodNames[i] +
" in frame " + frame + " but got " +
stack[frame].getMethodName());
}
frame--;
}
}
private static void printStack(Thread t, StackTraceElement[] stack) {
System.out.println(t +
" stack: (length = " + stack.length + ")");
if (t != null) {
for (int j = 0; j < stack.length; j++) {
System.out.println(stack[j]);
}
System.out.println();
}
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2000, 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 4363076
* @summary Basic functional test of Thread.holdsLock(Object)
* @author Josh Bloch and Steffen Grarup
*/
public class HoldsLock {
private static Object target = null;
private static void checkLock(boolean value) {
if (Thread.holdsLock(target) != value)
throw new RuntimeException("Should be " + value);
}
static class LockThread extends Thread {
public void run() {
checkLock(false);
synchronized(target) {
checkLock(true);
}
checkLock(false);
}
}
public static void main(String args[]) throws Exception {
// Test null obj case
try {
checkLock(false);
throw new RuntimeException("NullPointerException not thrown");
} catch (NullPointerException e) {
};
// Test uncontested case
target = new Object();
checkLock(false);
synchronized(target) {
checkLock(true);
}
checkLock(false);
// Test contested case
synchronized(target) {
checkLock(true);
new LockThread().start();
checkLock(true);
Thread.sleep(100);
checkLock(true);
}
checkLock(false);
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2015, 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 Thread(ThreadGroup,Runnable,String,long,boolean)
*/
public class ITLConstructor {
static InheritableThreadLocal<Integer> n = new InheritableThreadLocal<>() {
protected Integer initialValue() {
return 0;
}
protected Integer childValue(Integer parentValue) {
return parentValue + 1;
}
};
static final int CHILD_THREAD_COUNT = 10;
public static void main(String args[]) throws Exception {
test(true);
test(false);
}
static void test(boolean inherit) throws Exception {
// concurrent access to separate indexes is ok
int[] x = new int[CHILD_THREAD_COUNT];
Thread child = new Thread(Thread.currentThread().getThreadGroup(),
new AnotherRunnable(0, x, inherit),
"ITLConstructor-thread-"+(0),
0,
inherit);
child.start();
child.join(); // waits for *all* threads to complete
// Check results
for(int i=0; i<CHILD_THREAD_COUNT; i++) {
int expectedValue = 1;
if (inherit)
expectedValue = i+1;
if (x[i] != expectedValue)
throw (new Exception("Got x[" + i + "] = " + x[i]
+ ", expected: " + expectedValue));
}
}
static class AnotherRunnable implements Runnable {
final int threadId;
final int[] x;
final boolean inherit;
AnotherRunnable(int threadId, int[] x, boolean inherit) {
this.threadId = threadId;
this.x = x;
this.inherit = inherit;
}
public void run() {
int itlValue = n.get();
if (threadId < CHILD_THREAD_COUNT-1) {
Thread child = new Thread(Thread.currentThread().getThreadGroup(),
new AnotherRunnable(threadId+1, x, inherit),
"ITLConstructor-thread-" + (threadId+1),
0,
inherit);
child.start();
try {
child.join();
} catch(InterruptedException e) {
throw(new RuntimeException("Interrupted", e));
}
}
x[threadId] = itlValue+1;
}
}
}

View file

@ -0,0 +1,114 @@
/*
* Copyright Amazon.com Inc. 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 8305425
* @summary Check Thread.isAlive
* @run main/othervm IsAlive
*/
public class IsAlive {
static boolean spinnerDone;
private static void spin() {
try {
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(100);
}
} catch (InterruptedException ie) {
// Do nothing, just exit
}
spinnerDone = true;
}
static volatile boolean checkerReady;
private static void check(Thread t) {
while (!t.isAlive()) {
// Burn hard, without any sleeps.
// Check that we discover the thread is alive eventually.
}
checkerReady = true;
while (t.isAlive()) {
// Burn hard, without any sleeps.
// Check that we discover the thread is not alive eventually.
}
if (!spinnerDone) {
throw new RuntimeException("Last write of terminated thread was not seen!");
}
}
private static void assertAlive(Thread t) {
if (!t.isAlive()) {
throw new IllegalStateException("Thread " + t + " is not alive, but it should be");
}
}
private static void assertNotAlive(Thread t) {
if (t.isAlive()) {
throw new IllegalStateException("Thread " + t + " is alive, but it should not be");
}
}
public static void main(String args[]) throws Exception {
Thread spinner = new Thread(IsAlive::spin);
spinner.setName("Spinner");
spinner.setDaemon(true);
Thread checker = new Thread(() -> check(spinner));
checker.setName("Checker");
checker.setDaemon(true);
assertNotAlive(spinner);
assertNotAlive(checker);
System.out.println("Starting spinner");
spinner.start();
assertAlive(spinner);
System.out.println("Starting checker");
checker.start();
assertAlive(checker);
System.out.println("Waiting for checker to catch up");
while (!checkerReady) {
Thread.sleep(100);
}
System.out.println("Interrupting and joining spinner");
spinner.interrupt();
spinner.join();
assertNotAlive(spinner);
System.out.println("Joining checker");
checker.join();
assertNotAlive(checker);
System.out.println("Complete");
}
}

View file

@ -0,0 +1,216 @@
/*
* 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.join(Duration)
* @run junit JoinWithDuration
*/
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class JoinWithDuration {
/**
* Test join on unstarted thread.
*/
@Test
void testJoinOnUnstartedThread() {
var thread = new Thread(() -> { });
assertThrows(IllegalThreadStateException.class,
() -> thread.join(Duration.ofNanos(-100)));
assertThrows(IllegalThreadStateException.class,
() -> thread.join(Duration.ofNanos(0)));
assertThrows(IllegalThreadStateException.class,
() -> thread.join(Duration.ofNanos(100)));
}
/**
* Test join on thread that does not terminate while waiting.
*/
@Test
void testJoinOnRunningThread() throws Exception {
var thread = new Thread(LockSupport::park);
thread.start();
try {
assertFalse(thread.join(Duration.ofNanos(-100)));
assertFalse(thread.join(Duration.ofNanos(0)));
assertFalse(thread.join(Duration.ofNanos(100)));
// test duration of join
long start = millisTime();
assertFalse(thread.join(Duration.ofMillis(2000)));
expectDuration(start, /*min*/1900, /*max*/20_000);
} finally {
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test join on thread that terminates while waiting.
*/
@Test
void testJoinOnTerminatingThread() throws Exception {
var thread = new Thread(() -> {
try {
Thread.sleep(50);
} catch (InterruptedException e) { }
});
thread.start();
assertTrue(thread.join(Duration.ofMinutes(1)));
}
/**
* Test join on terminated thread.
*/
@Test
void testJoinOnTerminatedThread() throws Exception {
var thread = new Thread(() -> { });
thread.start();
thread.join();
assertTrue(thread.join(Duration.ofNanos(-100)));
assertTrue(thread.join(Duration.ofNanos(0)));
assertTrue(thread.join(Duration.ofNanos(100)));
}
/**
* Test invoking join with interrupted status set.
*/
@Test
void testJoinWithInterruptStatusSet() throws Exception {
var thread = new Thread(LockSupport::park);
thread.start();
Thread.currentThread().interrupt();
try {
assertThrows(InterruptedException.class,
() -> thread.join(Duration.ofMinutes(1)));
assertFalse(Thread.currentThread().isInterrupted());
} finally {
Thread.interrupted();
LockSupport.unpark(thread);
thread.join();
}
}
/**
* Test interrupting join.
*/
@Test
void testInterruptJoin() throws Exception {
// schedule current thread to be interrupted after 1s
Thread targetThread = Thread.currentThread();
Thread wakerThread = new Thread(() -> {
try {
Thread.sleep(1000);
targetThread.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
});
wakerThread.start();
var thread = new Thread(LockSupport::park);
thread.start();
try {
thread.join(Duration.ofMinutes(1));
fail();
} catch (InterruptedException e) {
// interrupted status should be cleared
assertFalse(thread.isInterrupted());
} finally {
LockSupport.unpark(thread);
thread.join();
wakerThread.join();
}
}
/**
* Test join on current thread.
*/
@Test
void testJoinSelf() throws Exception {
Thread thread = Thread.currentThread();
assertFalse(thread.join(Duration.ofNanos(-100)));
assertFalse(thread.join(Duration.ofNanos(0)));
assertFalse(thread.join(Duration.ofNanos(100)));
// test duration of join
long start = millisTime();
assertFalse(thread.join(Duration.ofMillis(2000)));
expectDuration(start, /*min*/1900, /*max*/20_000);
}
/**
* Test join(null).
*/
@Test
void testJoinNull() throws Exception {
var thread = new Thread(LockSupport::park);
// unstarted
assertThrows(NullPointerException.class, () -> thread.join(null));
// started
thread.start();
try {
assertThrows(NullPointerException.class, () -> thread.join(null));
} finally {
LockSupport.unpark(thread);
}
thread.join();
// terminated
assertThrows(NullPointerException.class, () -> thread.join(null));
}
/**
* Returns the current time in milliseconds.
*/
private static long millisTime() {
long now = System.nanoTime();
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
}
/**
* Check the duration of a task
* @param start start time, 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 expectDuration(long start, long min, long max) {
long duration = millisTime() - start;
assertTrue(duration >= min,
"Duration " + duration + "ms, expected >= " + min + "ms");
assertTrue(duration <= max,
"Duration " + duration + "ms, expected <= " + max + "ms");
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2003, 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
* @bug 4533087
* @summary Test to see if the main thread is in its thread group
* @requires test.thread.factory == null
*/
public class MainThreadTest {
public static void main(String args[]) {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
int n = tg.activeCount();
Thread[] ts = new Thread[n];
int m = tg.enumerate(ts);
for (int i = 0; i < ts.length; i++) {
if (Thread.currentThread() == ts[i]) {
return;
}
}
throw new RuntimeException(
"Current thread is not in its own thread group!");
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2007, 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 6571589
* @summary java.lang.Thread#getStackTrace() returns null.
*/
public class NullStackTrace
{
static final int TIMES = 1000;
public static void main(String[] args)
{
for (int i=0; i<TIMES; i++) {
Thread t = new Thread();
t.start();
StackTraceElement[] ste = t.getStackTrace();
if (ste == null)
throw new RuntimeException("Failed: Thread.getStackTrace should not return null");
}
}
}

View file

@ -0,0 +1,119 @@
/*
* Copyright Amazon.com Inc. 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 Sanity test Thread.sleep behavior
* @run junit SleepSanity
*/
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class SleepSanity {
static final int[] TRY_MILLIS = new int[] { 0, 1, 10, 100, 1_000 };
static final int[] TRY_NANOS = new int[] { 0, 1, 10, 100, 1_000, 10_000, 100_000, 999_999 };
@Test
void testMillis() throws Exception {
testIAE(() -> Thread.sleep(-1), "timeout value is negative");
testTimeout(() -> Thread.sleep(10_000), 5_000);
testTimeout(() -> Thread.sleep(Integer.MAX_VALUE), 5_000);
testTimeout(() -> Thread.sleep(Long.MAX_VALUE), 5_000);
for (final int millis : TRY_MILLIS) {
testTimes(() -> Thread.sleep(millis), millis, 20_000);
}
}
@Test
void testMillisNanos() throws Exception {
testIAE(() -> Thread.sleep(-1), "timeout value is negative");
testIAE(() -> Thread.sleep(0, -1), "nanosecond timeout value out of range");
testIAE(() -> Thread.sleep(0, 1_000_000), "nanosecond timeout value out of range");
testIAE(() -> Thread.sleep(0, Integer.MAX_VALUE), "nanosecond timeout value out of range");
testTimeout(() -> Thread.sleep(10_000, 0), 5_000);
testTimeout(() -> Thread.sleep(Integer.MAX_VALUE, 0), 5_000);
testTimeout(() -> Thread.sleep(Long.MAX_VALUE, 0), 5_000);
testTimeout(() -> Thread.sleep(10_000, 999_999), 5_000);
testTimeout(() -> Thread.sleep(Integer.MAX_VALUE, 999_999), 5_000);
testTimeout(() -> Thread.sleep(Long.MAX_VALUE, 999_999), 5_000);
for (final int millis : TRY_MILLIS) {
for (final int nanos : TRY_NANOS) {
testTimes(() -> Thread.sleep(millis, nanos), millis, 20_000);
}
}
}
private static void testTimes(TestCase t, long millisMin, long millisMax) throws Exception {
long start = System.nanoTime();
t.run();
long end = System.nanoTime();
long duration = TimeUnit.NANOSECONDS.toMillis(end - start);
assertTrue(duration >= millisMin, "Duration " + duration + "ms, expected >= " + millisMin + "ms");
assertTrue(duration <= millisMax, "Duration " + duration + "ms, expected <= " + millisMax + "ms");
}
private static void testTimeout(TestCase t, long millis) throws Exception {
Thread captThread = Thread.currentThread();
Thread watcher = new Thread(() -> {
try {
Thread.sleep(millis);
} catch (InterruptedException ie) {
// Do nothing
}
captThread.interrupt();
});
watcher.setDaemon(true);
watcher.start();
try {
t.run();
fail("Exited before timeout");
} catch (InterruptedException ie) {
// Expected
}
watcher.join();
}
private static void testIAE(TestCase t, String msg) throws Exception {
try {
t.run();
fail("Should have thrown the IAE");
} catch (IllegalArgumentException iae) {
assertTrue(iae.getMessage().contains(msg),
"Thrown IAE does not contain the string: " + msg + " " + iae);
}
}
private interface TestCase {
void run() throws Exception;
}
}

View file

@ -0,0 +1,126 @@
/*
* 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.sleep(Duration)
* @run junit SleepWithDuration
*/
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class SleepWithDuration {
/**
* Basic test for sleep(Duration).
*/
@Test
void testSleep() throws Exception {
// sleep for 2 seconds
long start = millisTime();
Thread.sleep(Duration.ofMillis(2000));
expectDuration(start, /*min*/1900, /*max*/20_000);
Thread.sleep(Duration.ofNanos(-1));
Thread.sleep(Duration.ofNanos(0));
Thread.sleep(Duration.ofNanos(1));
assertThrows(NullPointerException.class, () -> Thread.sleep(null));
}
/**
* Test Thread.sleep with interrupted status set.
*/
@Test
void testSleepWithInterruptStatusSet() throws Exception {
Thread.currentThread().interrupt();
try {
Thread.sleep(Duration.ofNanos(0));
fail();
} catch (InterruptedException e) {
assertFalse(Thread.interrupted());
}
Thread.currentThread().interrupt();
try {
Thread.sleep(Duration.ofSeconds(2));
fail();
} catch (InterruptedException e) {
assertFalse(Thread.interrupted());
}
}
/**
* Test interrupting Thread.sleep.
*/
@Test
void testInterruptSleep() throws Exception {
// schedule current thread to be interrupted after 1s
Thread targetThread = Thread.currentThread();
Thread wakerThread = new Thread(() -> {
try {
Thread.sleep(1000);
targetThread.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
});
wakerThread.start();
try {
Thread.sleep(Duration.ofSeconds(60));
fail();
} catch (InterruptedException e) {
// interrupted status should be cleared
assertFalse(Thread.interrupted());
} finally {
wakerThread.join();
}
}
/**
* Returns the current time in milliseconds.
*/
private static long millisTime() {
long now = System.nanoTime();
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
}
/**
* Check the duration of a task
* @param start start time, 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 expectDuration(long start, long min, long max) {
long duration = millisTime() - start;
assertTrue(duration >= min,
"Duration " + duration + "ms, expected >= " + min + "ms");
assertTrue(duration <= max,
"Duration " + duration + "ms, expected <= " + max + "ms");
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2007, 2011, 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.
*/
/*
* This test is relatively useful for verifying 6379235, but
* is too resource intensive, especially on 64 bit systems,
* to be run automatically, see 6721694.
*
* When run it should be typically be run with the server vm
* and a relatively small java heap, and a large stack size
* ( to provoke the OOM quicker ).
* java -server -Xmx32m -Xms32m -Xss256m StartOOMTest
*/
import java.util.*;
public class StartOOMTest
{
public static void main(String[] args) throws Throwable {
Runnable r = new SleepRunnable();
ThreadGroup tg = new ThreadGroup("buggy");
List<Thread> threads = new ArrayList<Thread>();
Thread failedThread;
int i = 0;
for (i = 0; ; i++) {
Thread t = new Thread(tg, r);
try {
t.start();
threads.add(t);
} catch (Throwable x) {
failedThread = t;
System.out.println(x);
System.out.println(i);
break;
}
}
int j = 0;
for (Thread t : threads)
t.interrupt();
while (tg.activeCount() > i/2)
Thread.yield();
failedThread.start();
failedThread.interrupt();
for (Thread t : threads)
t.join();
failedThread.join();
try {
Thread.sleep(1000);
} catch (Throwable ignore) {
}
int activeCount = tg.activeCount();
System.out.println("activeCount = " + activeCount);
if (activeCount > 0) {
throw new RuntimeException("Failed: there should be no active Threads in the group");
}
}
static class SleepRunnable implements Runnable
{
public void run() {
try {
Thread.sleep(60*1000);
} catch (Throwable t) {
}
}
}
}

View file

@ -0,0 +1,196 @@
/*
* 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 Test that Thread.sleep emits a JFR jdk.ThreadSleep event
* @requires vm.hasJFR
* @modules jdk.jfr
* @run junit ThreadSleepEvent
*/
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadFactory;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import static java.util.concurrent.TimeUnit.*;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordedEvent;
import jdk.jfr.consumer.RecordingFile;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
class ThreadSleepEvent {
private static final String THREAD_SLEEP_EVENT_NAME = "jdk.ThreadSleep";
static Stream<ThreadFactory> threadFactories() {
return Stream.of(
Thread.ofPlatform().factory(),
Thread.ofVirtual().factory()
);
}
// Arguments.of(ThreadFactory, millis)
static Stream<Arguments> threadFactoryAndMillis() {
long[] millis = {0, 1000, Long.MAX_VALUE};
return threadFactories()
.flatMap(f -> LongStream.of(millis)
.mapToObj(ms -> Arguments.of(f, ms)));
}
// Arguments.of(ThreadFactory, millis, nanos)
static Stream<Arguments> threadFactoryAndMillisAndNanos() {
int[] nanos = {0, 1000, 999_999};
return threadFactoryAndMillis().flatMap(a -> IntStream.of(nanos)
.mapToObj(ns -> Arguments.of(a.get()[0], a.get()[1], ns)));
}
// Arguments.of(ThreadFactory, Duration)
static Stream<Arguments> threadFactoryAndDuration() {
Duration[] durations = {
Duration.ofNanos(0),
Duration.ofSeconds(1),
Duration.ofSeconds(Long.MAX_VALUE, 999_999_999)
};
return threadFactories()
.flatMap(f -> Stream.of(durations)
.map(d -> Arguments.of(f, d)));
}
/**
* Test Thread.sleep(long) emits event.
*/
@ParameterizedTest
@MethodSource("threadFactoryAndMillis")
void testSleep(ThreadFactory factory, long millis) throws Exception {
long expectedTime = MILLISECONDS.toNanos(millis);
long recordedTime = testSleep(factory, () -> Thread.sleep(millis));
assertEquals(expectedTime, recordedTime);
}
/**
* Test Thread.sleep(long, int) emits event.
*/
@ParameterizedTest
@MethodSource("threadFactoryAndMillisAndNanos")
void testSleep(ThreadFactory factory, long millis, int nanos) throws Exception {
long expectedTime = MILLISECONDS.toNanos(millis);
expectedTime += Math.min(Long.MAX_VALUE - expectedTime, nanos);
long recordedTime = testSleep(factory, () -> Thread.sleep(millis, nanos));
assertEquals(expectedTime, recordedTime);
}
/**
* Test Thread.sleep(Duration) emits event.
*/
@ParameterizedTest
@MethodSource("threadFactoryAndDuration")
void testSleep(ThreadFactory factory, Duration duration) throws Exception {
long expectedTime = NANOSECONDS.convert(duration);
long recordedTime = testSleep(factory, () -> Thread.sleep(duration));
assertEquals(expectedTime, recordedTime);
}
/**
* Test that sleeper emits event, returning the recorded sleep time.
*/
long testSleep(ThreadFactory factory, Sleeper sleeper) throws Exception {
try (Recording recording = new Recording()) {
long tid;
recording.enable(THREAD_SLEEP_EVENT_NAME);
recording.start();
try {
// start thread to run sleeper task
var latch = new CountDownLatch(1);
Thread thread = factory.newThread(() -> {
latch.countDown();
try {
sleeper.run();
} catch (InterruptedException e) {
// ignore;
}
});
thread.start();
// wait for thread to execute
latch.await();
// don't wait for the sleep time
thread.interrupt();
thread.join();
tid = thread.threadId();
} finally {
recording.stop();
}
// find the ThreadSleep event recorded by the thread
RecordedEvent event = find(recording, THREAD_SLEEP_EVENT_NAME, tid).orElseThrow();
return event.getLong("time");
}
}
private interface Sleeper {
void run() throws InterruptedException;
}
/**
* Find a recorded event with the given name and recorded by a thread with the
* given thread ID.
*/
private static Optional<RecordedEvent> find(Recording recording,
String name,
long tid) throws Exception {
Path recordingFile = recordingFile(recording);
List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile);
return events.stream()
.filter(e -> e.getEventType().getName().equals(name)
&& e.getThread().getJavaThreadId() == tid)
.findAny();
}
/**
* Return the file path to the recording file.
*/
private static Path recordingFile(Recording recording) throws Exception {
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;
}
}

View file

@ -0,0 +1,96 @@
/*
* 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 Thread.sleep when emitting the JFR jdk.ThreadSleep event throws OOME
* @modules java.base/jdk.internal.event
* @compile/module=java.base jdk/internal/event/ThreadSleepEvent.java
* @run junit ThreadSleepEventThrows
*/
import jdk.internal.event.ThreadSleepEvent;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ThreadSleepEventThrows {
/**
* Test Thread.sleep when creating the ThreadSleepEvent fails with OOME.
*/
@Test
void testThreadSleepEventCreateThrows() throws Exception {
ThreadSleepEvent.setCreateThrows(true);
try {
testSleep();
} finally {
ThreadSleepEvent.setCreateThrows(false);
}
}
/**
* Test Thread.sleep when ThreadSleepEvent.begin fails with OOME.
*/
@Test
void testThreadSleepEventBeginThrows() throws Exception {
ThreadSleepEvent.setBeginThrows(true);
try {
testSleep();
} finally {
ThreadSleepEvent.setBeginThrows(false);
}
}
/**
* Test Thread.sleep when ThreadSleepEvent.commit fails with OOME.
*/
@Test
void testThreadSleepEventCommitThrows() throws Exception {
ThreadSleepEvent.setCommitThrows(true);
try {
testSleep();
} finally {
ThreadSleepEvent.setCommitThrows(false);
}
}
/**
* Test Thread.sleep takes a minimum duration and doesn't throw.
*/
private void testSleep() throws Exception {
long start = currentTimeMillis();
Thread.sleep(SLEEP_TIME);
long duration = currentTimeMillis() - start;
assertTrue(duration >= MIN_EXPECTED_DURATION,
"Duration " + duration + "ms, expected >= " + MIN_EXPECTED_DURATION + "ms");
}
private static final int SLEEP_TIME = 2000;
private static final int MIN_EXPECTED_DURATION = 1900;
private static long currentTimeMillis() {
return NANOSECONDS.toMillis(System.nanoTime());
}
}

View file

@ -0,0 +1,375 @@
/*
* Copyright (c) 2013, 2020, 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.
*/
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
import jdk.test.lib.LockFreeLogger;
import jdk.test.lib.Utils;
/**
* ThreadStateController allows a thread to request this thread to transition
* to a specific thread state. The {@linkplain #transitionTo request} is
* a blocking call that the calling thread will wait until this thread is about
* going to the new state. Only one request of state transition at a time
* is supported (the Phaser expects only parties of 2 to arrive and advance
* to next phase).
*/
public class ThreadStateController extends Thread {
// used to achieve waiting states
private final Object lock;
public ThreadStateController(String name, Object lock) {
super(name);
this.lock = lock;
}
public void checkThreadState(Thread.State expected) {
// maximum number of retries when checking for thread state.
final int MAX_RETRY = 500;
// wait for the thread to transition to the expected state.
// There is a small window between the thread checking the state
// and the thread actual entering that state.
Thread.State state;
int retryCount=0;
while ((state = getState()) != expected && retryCount < MAX_RETRY) {
pause(10);
retryCount++;
}
if (state == null) {
throw new RuntimeException(getName() + " expected to have " +
expected + " but got null.");
}
if (state != expected) {
throw new RuntimeException(String.format("%s expected in %s state but got %s " +
"(iterations %d interrupted %d)%n",
getName(), expected, state, iterations.get(), interrupted.get()));
}
}
public static void pause(long ms) {
try {
Thread.sleep(Utils.adjustTimeout(ms));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// Phaser to sync between the main thread putting
// this thread into various states
private final Phaser phaser = new Phaser(2);
private volatile int newState = S_RUNNABLE;
private volatile int state = 0;
private boolean done = false;
private static final int S_RUNNABLE = 1;
private static final int S_BLOCKED = 2;
private static final int S_WAITING = 3;
private static final int S_TIMED_WAITING = 4;
private static final int S_PARKED = 5;
private static final int S_TIMED_PARKED = 6;
private static final int S_SLEEPING = 7;
private static final int S_TERMINATE = 8;
// for debugging
private final AtomicInteger iterations = new AtomicInteger();
private final AtomicInteger interrupted = new AtomicInteger();
private final LockFreeLogger logger = new LockFreeLogger();
@Override
public void run() {
// this thread has started
while (!done) {
// state transition
int nextState = state;
if (newState != state) {
nextState = newState;
iterations.set(0);
interrupted.set(0);
}
iterations.incrementAndGet();
switch (nextState) {
case S_RUNNABLE: {
stateChange(nextState);
double sum = 0;
for (int i = 0; i < 1000; i++) {
double r = Math.random();
double x = Math.pow(3, r);
sum += x - r;
}
break;
}
case S_BLOCKED: {
log("%d: %s is going to block (iterations %d)%n",
getId(), getName(), iterations.get());
stateChange(nextState);
// going to block on lock
synchronized (lock) {
log("%d: %s acquired the lock (iterations %d)%n",
getId(), getName(), iterations.get());
try {
// this thread has escaped the BLOCKED state
// release the lock and a short wait before continue
lock.wait(Utils.adjustTimeout(10));
} catch (InterruptedException e) {
// ignore
interrupted.incrementAndGet();
}
}
break;
}
case S_WAITING: {
synchronized (lock) {
log("%d: %s is going to waiting (iterations %d interrupted %d)%n",
getId(), getName(), iterations.get(), interrupted.get());
try {
stateChange(nextState);
lock.wait();
log("%d: %s wakes up from waiting (iterations %d interrupted %d)%n",
getId(), getName(), iterations.get(), interrupted.get());
} catch (InterruptedException e) {
// ignore
interrupted.incrementAndGet();
}
}
break;
}
case S_TIMED_WAITING: {
synchronized (lock) {
log("%d: %s is going to timed waiting (iterations %d interrupted %d)%n",
getId(), getName(), iterations.get(), interrupted.get());
try {
stateChange(nextState);
lock.wait(Integer.MAX_VALUE);
log("%d: %s wakes up from timed waiting (iterations %d interrupted %d)%n",
getId(), getName(), iterations.get(), interrupted.get());
} catch (InterruptedException e) {
// ignore
interrupted.incrementAndGet();
}
}
break;
}
case S_PARKED: {
log("%d: %s is going to park (iterations %d)%n",
getId(), getName(), iterations.get());
stateChange(nextState);
LockSupport.park();
break;
}
case S_TIMED_PARKED: {
log("%d: %s is going to timed park (iterations %d)%n",
getId(), getName(), iterations.get());
long deadline = System.currentTimeMillis() +
Utils.adjustTimeout(10000*1000);
stateChange(nextState);
LockSupport.parkUntil(deadline);
break;
}
case S_SLEEPING: {
log("%d: %s is going to sleep (iterations %d interrupted %d)%n",
getId(), getName(), iterations.get(), interrupted.get());
try {
stateChange(nextState);
Thread.sleep(Utils.adjustTimeout(1000000));
} catch (InterruptedException e) {
// finish sleeping
interrupted.incrementAndGet();
}
break;
}
case S_TERMINATE: {
done = true;
stateChange(nextState);
break;
}
default:
break;
}
}
}
/**
* Change the state if it matches newState.
*/
private void stateChange(int nextState) {
// no state change
if (state == nextState)
return;
// transition to the new state
if (newState == nextState) {
state = nextState;
phaser.arrive();
log("%d: state change: %s %s%n",
getId(), toStateName(nextState), phaserToString(phaser));
return;
}
// should never reach here
throw new RuntimeException("current " + state + " next " + nextState +
" new state " + newState);
}
/**
* Blocks until this thread transitions to the given state
*/
public void transitionTo(Thread.State tstate) throws InterruptedException {
switch (tstate) {
case RUNNABLE:
nextState(S_RUNNABLE);
break;
case BLOCKED:
nextState(S_BLOCKED);
break;
case WAITING:
nextState(S_WAITING);
break;
case TIMED_WAITING:
nextState(S_TIMED_WAITING);
break;
case TERMINATED:
nextState(S_TERMINATE);
break;
default:
break;
}
}
/**
* Blocks until this thread transitions to sleeping
*/
public void transitionToSleep() throws InterruptedException {
nextState(S_SLEEPING);
}
/**
* Blocks until this thread transitions to park or timed park
*/
public void transitionToPark(boolean timed) throws InterruptedException {
nextState(timed ? S_TIMED_PARKED : S_PARKED);
}
private void nextState(int s) throws InterruptedException {
final long id = Thread.currentThread().getId();
log("%d: wait until the thread transitions to %s %s%n",
id, toStateName(s), phaserToString(phaser));
this.newState = s;
int phase = phaser.arrive();
log("%d: awaiting party arrive %s %s%n",
id, toStateName(s), phaserToString(phaser));
for (;;) {
// when this thread has changed its state before it waits or parks
// on a lock, a potential race might happen if it misses the notify
// or unpark. Hence await for the phaser to advance with timeout
// to cope with this race condition.
switch (state) {
case S_WAITING:
case S_TIMED_WAITING:
synchronized (lock) {
lock.notify();
}
break;
case S_PARKED:
case S_TIMED_PARKED:
LockSupport.unpark(this);
break;
case S_SLEEPING:
this.interrupt();
break;
case S_BLOCKED:
default:
break;
}
try {
phaser.awaitAdvanceInterruptibly(phase, 100, TimeUnit.MILLISECONDS);
log("%d: arrived at %s %s%n",
id, toStateName(s), phaserToString(phaser));
return;
} catch (TimeoutException ex) {
// this thread hasn't arrived at this phase
log("%d: Timeout: %s%n", id, phaser);
}
}
}
private String phaserToString(Phaser p) {
return "[phase = " + p.getPhase() +
" parties = " + p.getRegisteredParties() +
" arrived = " + p.getArrivedParties() + "]";
}
private String toStateName(int state) {
switch (state) {
case S_RUNNABLE:
return "runnable";
case S_WAITING:
return "waiting";
case S_TIMED_WAITING:
return "timed waiting";
case S_PARKED:
return "parked";
case S_TIMED_PARKED:
return "timed parked";
case S_SLEEPING:
return "sleeping";
case S_BLOCKED:
return "blocked";
case S_TERMINATE:
return "terminated";
default:
return "unknown " + state;
}
}
private void log(String msg, Object ... params) {
logger.log(msg, params);
}
/**
* Waits for the controller to complete the test run and returns the
* generated log
* @return The controller log
* @throws InterruptedException
*/
public String getLog() throws InterruptedException {
return getLog(0);
}
/**
* Waits at most {@code millis} milliseconds for the controller
* to complete the test run and returns the generated log.
* A timeout of {@code 0} means to wait forever.
*/
public String getLog(long millis) throws InterruptedException {
this.join(millis);
return logger.toString();
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2004, 2013, 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.
*/
import static java.lang.Thread.State.*;
/*
* @test
* @bug 5014783 8022208
* @summary Basic unit test of thread states returned by
* Thread.getState().
*
* @author Mandy Chung
* @library /test/lib
* @build jdk.test.lib.LockFreeLogger
* @build ThreadStateTest ThreadStateController
* @run main/othervm -Xmixed ThreadStateTest
*/
public class ThreadStateTest {
private static boolean testFailed = false;
// used to achieve waiting states
private static final Object globalLock = new Object();
public static void main(String[] argv) throws Exception {
// Call Thread.getState to force all initialization done
// before test verification begins.
Thread.currentThread().getState();
ThreadStateController thread = new ThreadStateController("StateChanger", globalLock);
thread.setDaemon(true);
// before myThread starts
thread.checkThreadState(NEW);
thread.start();
thread.transitionTo(RUNNABLE);
thread.checkThreadState(RUNNABLE);
synchronized (globalLock) {
thread.transitionTo(BLOCKED);
thread.checkThreadState(BLOCKED);
}
thread.transitionTo(WAITING);
thread.checkThreadState(WAITING);
thread.transitionTo(TIMED_WAITING);
thread.checkThreadState(TIMED_WAITING);
thread.transitionToPark(true /* timed park*/);
thread.checkThreadState(TIMED_WAITING);
thread.transitionToPark(false /* indefinite park */);
thread.checkThreadState(WAITING);
thread.transitionToSleep();
thread.checkThreadState(TIMED_WAITING);
thread.transitionTo(TERMINATED);
thread.checkThreadState(TERMINATED);
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("Unexpected exception.");
testFailed = true;
}
if (testFailed)
throw new RuntimeException("TEST FAILED.");
System.out.println("Test passed.");
}
}

View file

@ -0,0 +1,226 @@
/*
* Copyright (c) 2004, 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.
*/
import java.util.stream.Stream;
import static java.lang.System.err;
import static java.lang.System.out;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/*
* @test
* @bug 4833089 4992454
* @summary Check for proper handling of uncaught exceptions
* @author Martin Buchholz
* @requires test.thread.factory == null
* @library /test/lib
* @build jdk.test.lib.process.*
* @run junit UncaughtExceptionsTest
*/
class UncaughtExceptionsTest {
private static Stream<Arguments> testCases() {
return Stream.of(
Arguments.of("ThreadIsDeadAfterJoin",
0,
UncaughtExitSimulator.EXPECTED_RESULT,
"Exception in thread \"Thread-\\d+\".*simulateUncaughtExitEvent"),
Arguments.of("MainThreadAbruptTermination",
1,
UncaughtExitSimulator.EXPECTED_RESULT,
"Exception in thread \"main\".*simulateUncaughtExitEvent"),
Arguments.of("MainThreadNormalTermination",
0,
UncaughtExitSimulator.EXPECTED_RESULT,
""),
Arguments.of("DefaultUncaughtExceptionHandlerOnMainThread",
1,
UncaughtExitSimulator.EXPECTED_RESULT,
""),
Arguments.of("DefaultUncaughtExceptionHandlerOnMainThreadOverride",
1,
UncaughtExitSimulator.EXPECTED_RESULT,
""),
Arguments.of("DefaultUncaughtExceptionHandlerOnNonMainThreadOverride",
0,
UncaughtExitSimulator.EXPECTED_RESULT,
""),
Arguments.of("DefaultUncaughtExceptionHandlerOnNonMainThread",
0,
UncaughtExitSimulator.EXPECTED_RESULT,
""),
Arguments.of("ThreadGroupUncaughtExceptionHandlerOnNonMainThread",
0,
UncaughtExitSimulator.EXPECTED_RESULT,
"")
);
}
@ParameterizedTest
@MethodSource("testCases")
void test(String className, int exitValue, String stdOutMatch, String stdErrMatch) throws Throwable {
String cmd = "UncaughtExitSimulator$" + className;
ProcessBuilder processBuilder = ProcessTools.createTestJavaProcessBuilder(cmd);
OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(processBuilder);
outputAnalyzer.shouldHaveExitValue(exitValue);
outputAnalyzer.stderrShouldMatch(stdErrMatch);
outputAnalyzer.stdoutShouldMatch(stdOutMatch);
}
}
class OK implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
out.println(UncaughtExitSimulator.EXPECTED_RESULT);
}
}
class NeverInvoked implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
err.println("Test failure: This handler should never be invoked!");
}
}
class UncaughtExitSimulator extends Thread implements Runnable {
final static String EXPECTED_RESULT = "OK";
public static void throwRuntimeException() {
throw new RuntimeException("simulateUncaughtExitEvent");
}
public void run() { throwRuntimeException(); }
/**
* A thread is never alive after you've join()ed it.
*/
public static class ThreadIsDeadAfterJoin extends UncaughtExitSimulator {
public static void main(String[] args) throws Exception {
Thread t = new UncaughtExitSimulator();
t.start(); t.join();
if (! t.isAlive()) {
out.println(EXPECTED_RESULT);
}
}
}
/**
* Even the main thread is mortal - here it terminates "abruptly"
*/
public static class MainThreadAbruptTermination extends UncaughtExitSimulator {
public static void main(String[] args) {
final Thread mainThread = currentThread();
new Thread() { public void run() {
try { mainThread.join(); }
catch (InterruptedException e) {}
if (! mainThread.isAlive())
out.println(EXPECTED_RESULT);
}}.start();
throwRuntimeException();
}
}
/**
* Even the main thread is mortal - here it terminates normally.
*/
public static class MainThreadNormalTermination extends UncaughtExitSimulator {
public static void main(String[] args) {
final Thread mainThread = currentThread();
new Thread() {
public void run() {
try {
mainThread.join();
} catch (InterruptedException e) {
}
if (!mainThread.isAlive())
out.println(EXPECTED_RESULT);
}
}.start();
}
}
/**
* Check uncaught exception handler mechanism on the main thread.
*/
public static class DefaultUncaughtExceptionHandlerOnMainThread extends UncaughtExitSimulator {
public static void main(String[] args) {
currentThread().setUncaughtExceptionHandler(new OK());
setDefaultUncaughtExceptionHandler(new NeverInvoked());
throwRuntimeException();
}
}
/**
* Check that thread-level handler overrides global default handler.
*/
public static class DefaultUncaughtExceptionHandlerOnMainThreadOverride extends UncaughtExitSimulator {
public static void main(String[] args) {
setDefaultUncaughtExceptionHandler(new OK());
throwRuntimeException();
}
}
/**
* Check uncaught exception handler mechanism on non-main threads.
*/
public static class DefaultUncaughtExceptionHandlerOnNonMainThreadOverride extends UncaughtExitSimulator {
public static void main(String[] args) {
Thread t = new UncaughtExitSimulator();
t.setUncaughtExceptionHandler(new OK());
t.start();
}
}
/**
* Check uncaught exception handler mechanism on non-main threads.
*/
public static class DefaultUncaughtExceptionHandlerOnNonMainThread extends UncaughtExitSimulator {
public static void main(String[] args) {
setDefaultUncaughtExceptionHandler(new OK());
new UncaughtExitSimulator().start();
}
}
/**
* Test ThreadGroup based uncaught exception handler mechanism.
* Since the handler for the main thread group cannot be changed,
* there are no tests for the main thread here.
*/
public static class ThreadGroupUncaughtExceptionHandlerOnNonMainThread extends UncaughtExitSimulator {
public static void main(String[] args) {
setDefaultUncaughtExceptionHandler(new NeverInvoked());
new Thread(
new ThreadGroup(EXPECTED_RESULT) {
public void uncaughtException(Thread t, Throwable e) {
out.println(EXPECTED_RESULT);
}
},
new UncaughtExitSimulator()
).start();
}
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.event;
/**
* ThreadSleepEvent to optionally throw OOME at create, begin or commit time.
*/
public class ThreadSleepEvent extends Event {
private static boolean throwOnCreate;
private static boolean throwOnBegin;
private static boolean throwOnCommit;
public long time;
public static boolean isTurnedOn() {
return true;
}
public static void setCreateThrows(boolean value) {
throwOnCreate = value;
}
public static void setBeginThrows(boolean value) {
throwOnBegin = value;
}
public static void setCommitThrows(boolean value) {
throwOnCommit = value;
}
public ThreadSleepEvent() {
if (throwOnCreate) {
throw new OutOfMemoryError();
}
}
@Override
public void begin() {
if (throwOnBegin) {
throw new OutOfMemoryError();
}
}
@Override
public void commit() {
if (throwOnCommit) {
throw new OutOfMemoryError();
}
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2022, 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
* @bug 8287982
* @summary Test native threads attaching to the VM with JNI AttachCurrentThread
* @requires (os.family == "linux" | os.family == "mac")
* @library /test/lib
* @compile ExplicitAttach.java
* @run main AttachTest ExplicitAttach 1
* @run main AttachTest ExplicitAttach 2
* @run main AttachTest ExplicitAttach 4
*/
/**
* @test
* @summary Test native threads attaching implicitly to the VM by means of an upcall
* @requires (os.family == "linux" | os.family == "mac") & (sun.arch.data.model == "64")
* @library /test/lib
* @compile ImplicitAttach.java
* @run main AttachTest --enable-native-access=ALL-UNNAMED ImplicitAttach 1
* @run main AttachTest --enable-native-access=ALL-UNNAMED ImplicitAttach 2
* @run main AttachTest --enable-native-access=ALL-UNNAMED ImplicitAttach 4
*/
import java.util.stream.Stream;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
public class AttachTest {
static final String TEST_CLASSES = System.getProperty("test.classes");
static final String JAVA_LIBRARY_PATH = System.getProperty("java.library.path");
public static void main(String[] args) throws Exception {
// prepend -cp ${test.classes} -Djava.library.path=${java.library.path}
String[] opts = Stream.concat(Stream.of(
"-cp", TEST_CLASSES,
"-Djava.library.path=" + JAVA_LIBRARY_PATH),
Stream.of(args))
.toArray(String[]::new);
OutputAnalyzer outputAnalyzer = ProcessTools
.executeTestJava(opts)
.outputTo(System.out)
.errorTo(System.out);
outputAnalyzer.shouldHaveExitValue(0);
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2022, 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.
*/
import java.util.concurrent.CountDownLatch;
/**
* Test native threads attaching to the VM with JNI AttachCurrentThread.
*/
public class ExplicitAttach {
private static volatile CountDownLatch latch;
public static void main(String[] args) throws Exception {
int threadCount;
if (args.length > 0) {
threadCount = Integer.parseInt(args[0]);
} else {
threadCount = 2;
}
latch = new CountDownLatch(threadCount);
// start the threads and wait for the threads to call home
startThreads(threadCount);
latch.await();
}
/**
* Invoked by attached threads.
*/
private static void callback() {
System.out.println(Thread.currentThread());
latch.countDown();
}
/**
* Start n native threads that attach to the VM and invoke callback.
*/
private static native void startThreads(int n);
static {
System.loadLibrary("ExplicitAttach");
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2022, 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.
*/
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.CountDownLatch;
/**
* Test native threads attaching implicitly to the VM by means of an upcall.
*/
public class ImplicitAttach {
private static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT;
private static final AddressLayout C_POINTER = ValueLayout.ADDRESS;
private static volatile CountDownLatch latch;
public static void main(String[] args) throws Throwable {
int threadCount;
if (args.length > 0) {
threadCount = Integer.parseInt(args[0]);
} else {
threadCount = 2;
}
latch = new CountDownLatch(threadCount);
Linker abi;
try {
abi = Linker.nativeLinker();
} catch (UnsupportedOperationException e) {
System.out.println("Test skipped, no native linker on this platform");
return;
}
// stub to invoke callback
MethodHandle callback = MethodHandles.lookup()
.findStatic(ImplicitAttach.class, "callback", MethodType.methodType(void.class));
MemorySegment upcallStub = abi.upcallStub(callback,
FunctionDescriptor.ofVoid(),
Arena.global());
// void start_threads(int count, void *(*f)(void *))
SymbolLookup symbolLookup = SymbolLookup.loaderLookup();
MemorySegment symbol = symbolLookup.find("start_threads").orElseThrow();
FunctionDescriptor desc = FunctionDescriptor.ofVoid(C_INT, C_POINTER);
MethodHandle start_threads = abi.downcallHandle(symbol, desc);
// start the threads and wait for the threads to call home
start_threads.invoke(threadCount, upcallStub);
latch.await();
}
/**
* Invoked from native thread.
*/
private static void callback() {
System.out.println(Thread.currentThread());
latch.countDown();
}
static {
System.loadLibrary("ImplicitAttach");
}
}

View file

@ -0,0 +1,88 @@
/*
* 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.
*/
#include <stdio.h>
#include <pthread.h>
#include "jni.h"
#define STACK_SIZE 0x100000
static JavaVM *vm;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* reserved) {
vm = jvm;
return JNI_VERSION_1_8;
}
/**
* Attach the current thread with JNI AttachCurrentThread, call a method, and detach.
*/
void* thread_main(void* arg) {
JNIEnv *env;
jint res;
res = (*vm)->AttachCurrentThread(vm, (void **) &env, NULL);
if (res != JNI_OK) {
fprintf(stderr, "AttachCurrentThreadAsDaemon failed: %d\n", res);
return NULL;
}
// call ExplicitAttach.callback()
jclass clazz = (*env)->FindClass(env, "ExplicitAttach");
if (clazz == NULL) {
fprintf(stderr, "FindClass failed\n");
goto detach;
}
jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "callback", "()V");
if (mid == NULL) {
fprintf(stderr, "GetStaticMethodID failed\n");
goto detach;
}
(*env)->CallStaticVoidMethod(env, clazz, mid);
if ((*env)->ExceptionOccurred(env)) {
fprintf(stderr, "CallStaticVoidMethod failed\n");
goto detach;
}
detach:
res = (*vm)->DetachCurrentThread(vm);
if (res != JNI_OK) {
fprintf(stderr, "DetachCurrentThread failed: %d\n", res);
}
return NULL;
}
JNIEXPORT void JNICALL Java_ExplicitAttach_startThreads(JNIEnv *env, jclass clazz, int n) {
pthread_t tid;
pthread_attr_t attr;
int i;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, STACK_SIZE);
for (i = 0; i < n ; i++) {
int res = pthread_create(&tid, &attr, thread_main, NULL);
if (res != 0) {
fprintf(stderr, "pthread_create failed: %d\n", res);
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2022, 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 <pthread.h>
#include <stdio.h>
#include "export.h"
#define STACK_SIZE 0x100000
/**
* Creates n threads to execute the given function.
*/
EXPORT void start_threads(int n, void *(*f)(void *)) {
pthread_t tid;
pthread_attr_t attr;
int i;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, STACK_SIZE);
for (i = 0; i < n ; i++) {
int res = pthread_create(&tid, &attr, f, NULL);
if (res != 0) {
fprintf(stderr, "pthread_create failed: %d\n", res);
}
}
}

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

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

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

View 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) { }
}
}
}

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

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

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

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

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

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

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

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

View 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");
}
}

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

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

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

View file

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

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

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

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

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

File diff suppressed because it is too large Load diff

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

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

View file

@ -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!!!");
}
}

View 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"));
}
}

View 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
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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;
}
}

View file

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

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

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

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

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

View 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;
}
}
}

View file

@ -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;
}
}
}

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

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

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

View 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.
```