undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
805
test/jdk/java/lang/Thread/BuilderTest.java
Normal file
805
test/jdk/java/lang/Thread/BuilderTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
189
test/jdk/java/lang/Thread/GenerifyStackTraces.java
Normal file
189
test/jdk/java/lang/Thread/GenerifyStackTraces.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
test/jdk/java/lang/Thread/HoldsLock.java
Normal file
75
test/jdk/java/lang/Thread/HoldsLock.java
Normal 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);
|
||||
}
|
||||
}
|
||||
100
test/jdk/java/lang/Thread/ITLConstructor.java
Normal file
100
test/jdk/java/lang/Thread/ITLConstructor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
test/jdk/java/lang/Thread/IsAlive.java
Normal file
114
test/jdk/java/lang/Thread/IsAlive.java
Normal 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");
|
||||
}
|
||||
}
|
||||
216
test/jdk/java/lang/Thread/JoinWithDuration.java
Normal file
216
test/jdk/java/lang/Thread/JoinWithDuration.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
45
test/jdk/java/lang/Thread/MainThreadTest.java
Normal file
45
test/jdk/java/lang/Thread/MainThreadTest.java
Normal 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!");
|
||||
}
|
||||
}
|
||||
45
test/jdk/java/lang/Thread/NullStackTrace.java
Normal file
45
test/jdk/java/lang/Thread/NullStackTrace.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
119
test/jdk/java/lang/Thread/SleepSanity.java
Normal file
119
test/jdk/java/lang/Thread/SleepSanity.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
126
test/jdk/java/lang/Thread/SleepWithDuration.java
Normal file
126
test/jdk/java/lang/Thread/SleepWithDuration.java
Normal 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");
|
||||
}
|
||||
}
|
||||
93
test/jdk/java/lang/Thread/StartOOMTest.java
Normal file
93
test/jdk/java/lang/Thread/StartOOMTest.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
test/jdk/java/lang/Thread/ThreadSleepEvent.java
Normal file
196
test/jdk/java/lang/Thread/ThreadSleepEvent.java
Normal 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;
|
||||
}
|
||||
}
|
||||
96
test/jdk/java/lang/Thread/ThreadSleepEventThrows.java
Normal file
96
test/jdk/java/lang/Thread/ThreadSleepEventThrows.java
Normal 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());
|
||||
}
|
||||
}
|
||||
375
test/jdk/java/lang/Thread/ThreadStateController.java
Normal file
375
test/jdk/java/lang/Thread/ThreadStateController.java
Normal 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();
|
||||
}
|
||||
}
|
||||
94
test/jdk/java/lang/Thread/ThreadStateTest.java
Normal file
94
test/jdk/java/lang/Thread/ThreadStateTest.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
226
test/jdk/java/lang/Thread/UncaughtExceptionsTest.java
Normal file
226
test/jdk/java/lang/Thread/UncaughtExceptionsTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
test/jdk/java/lang/Thread/virtual/BigStackChunk.java
Normal file
96
test/jdk/java/lang/Thread/virtual/BigStackChunk.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @bug 8335362
|
||||
* @summary Test virtual thread usage with big stackChunks
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm BigStackChunk
|
||||
*/
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class BigStackChunk {
|
||||
|
||||
void recurse(int cnt, ReentrantLock rlock) {
|
||||
int i1 = cnt;
|
||||
int i2 = i1 + 1;
|
||||
int i3 = i2 + 1;
|
||||
int i4 = i3 + 1;
|
||||
int i5 = i4 + 1;
|
||||
int i6 = i5 + 1;
|
||||
int i7 = i6 + 1;
|
||||
long ll = 2 * (long)i1;
|
||||
float ff = ll + 1.2f;
|
||||
double dd = ff + 1.3D;
|
||||
|
||||
if (cnt > 0) {
|
||||
recurse(cnt - 1, rlock);
|
||||
} else {
|
||||
rlock.lock();
|
||||
rlock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bigStackChunkTest() throws Exception {
|
||||
int VTHREAD_CNT = Runtime.getRuntime().availableProcessors();
|
||||
ReentrantLock rlock = new ReentrantLock();
|
||||
Thread[] vthreads = new Thread[VTHREAD_CNT];
|
||||
|
||||
rlock.lock();
|
||||
for (int i = 0; i < VTHREAD_CNT; i++) {
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
// Set up things so that half of the carriers will commit lots of
|
||||
// pages in the stack while running the mounted vthread and half
|
||||
// will just commit very few ones.
|
||||
if (Math.random() < 0.5) {
|
||||
recurse(300, rlock);
|
||||
} else {
|
||||
recurse(1, rlock);
|
||||
}
|
||||
});
|
||||
}
|
||||
await(vthreads[0], Thread.State.WAITING);
|
||||
// Now we expect that some vthread that recursed a lot is mounted on
|
||||
// a carrier that previously run a vthread that didn't recurse at all.
|
||||
rlock.unlock();
|
||||
|
||||
for (int i = 0; i < VTHREAD_CNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
103
test/jdk/java/lang/Thread/virtual/CancelTimerWithContention.java
Normal file
103
test/jdk/java/lang/Thread/virtual/CancelTimerWithContention.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test cancelling a timeout task for Object.wait(millis) when there is
|
||||
* contention on the timer queue
|
||||
* @requires vm.continuations
|
||||
* @key randomness
|
||||
* @run main/othervm/timeout=480
|
||||
* -Djdk.virtualThreadScheduler.parallelism=2
|
||||
* -Djdk.virtualThreadScheduler.timerQueues=1
|
||||
* CancelTimerWithContention
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CancelTimerWithContention {
|
||||
|
||||
// number of threads
|
||||
private static final int MIN_THREADS = 100;
|
||||
private static final int MAX_THREADS = 10_000;
|
||||
|
||||
// number of monitors to enter
|
||||
private static final int MIN_MONITORS = 2;
|
||||
private static final int MAX_MONITORS = 8;
|
||||
|
||||
private static final Random RAND = new Random();
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int threadCount = MIN_THREADS; threadCount <= MAX_THREADS; threadCount += 100) {
|
||||
System.out.format("%s #threads = %d%n", Instant.now(), threadCount);
|
||||
for (int lockCount = MIN_MONITORS; lockCount <= MAX_MONITORS; lockCount += 2) {
|
||||
test(threadCount, lockCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test threads entering monitors and using Object.wait(millis) to wait. This
|
||||
* testing scenario leads to a mix of contention (with responsible threads using
|
||||
* short timeouts), timed-wait, and cancellation of timer tasks. This scenario
|
||||
* can result in contention of the timer queue.
|
||||
*/
|
||||
static void test(int threadCount, int monitorCount) {
|
||||
var locks = new Object[monitorCount];
|
||||
for (int i = 0; i < monitorCount; i++) {
|
||||
locks[i] = new Object();
|
||||
}
|
||||
|
||||
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
var finished = new AtomicInteger();
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
Object lock = locks[RAND.nextInt(monitorCount)]; // random lock
|
||||
|
||||
executor.submit(() -> {
|
||||
synchronized (lock) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
}
|
||||
finished.incrementAndGet();
|
||||
return null;
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// notify at most one thread until all threads are finished
|
||||
while (finished.get() < threadCount) {
|
||||
for (Object lock : locks) {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
219
test/jdk/java/lang/Thread/virtual/CarrierThreadInfo.java
Normal file
219
test/jdk/java/lang/Thread/virtual/CarrierThreadInfo.java
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test id=default
|
||||
* @bug 8284161 8286788
|
||||
* @summary Test java.lang.management.ThreadInfo contains expected information for carrier threads
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit CarrierThreadInfo
|
||||
*/
|
||||
|
||||
import java.lang.management.LockInfo;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class CarrierThreadInfo {
|
||||
|
||||
/**
|
||||
* Test that ThreadInfo.getLockedMonitors returns information about a lock held by
|
||||
* a carrier thread.
|
||||
*/
|
||||
@Test
|
||||
void testCarrierThreadHoldsLock() throws Exception {
|
||||
Object lock = new Object();
|
||||
ThreadFactory factory = task -> Thread.ofPlatform().unstarted(() -> {
|
||||
synchronized (lock) {
|
||||
task.run();
|
||||
}
|
||||
});
|
||||
|
||||
try (var scheduler = new CustomScheduler(factory)) {
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
Thread vthread = scheduler.forkVirtualThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
awaitTrue(started);
|
||||
|
||||
// carrier threads holds the lock
|
||||
long carrierId = scheduler.carrier().threadId();
|
||||
ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
|
||||
.getThreadInfo(new long[] { carrierId }, true, true)[0];
|
||||
boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
|
||||
.anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
|
||||
assertTrue(holdsLock, "Carrier should hold lock");
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ThreadInfo.getLockedMonitors does not return information about a lock
|
||||
* held by mounted virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testVirtualThreadHoldsLock() throws Exception {
|
||||
ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
try (var scheduler = new CustomScheduler(factory)) {
|
||||
var started = new AtomicBoolean();
|
||||
var lock = new Object();
|
||||
var done = new AtomicBoolean();
|
||||
Thread vthread = scheduler.forkVirtualThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
awaitTrue(started);
|
||||
|
||||
// carrier threads does not hold lock
|
||||
long carrierId = scheduler.carrier().threadId();
|
||||
ThreadInfo threadInfo = ManagementFactory.getPlatformMXBean(ThreadMXBean.class)
|
||||
.getThreadInfo(new long[] { carrierId }, true, true)[0];
|
||||
boolean holdsLock = Arrays.stream(threadInfo.getLockedMonitors())
|
||||
.anyMatch(mi -> mi.getIdentityHashCode() == System.identityHashCode(lock));
|
||||
assertFalse(holdsLock, "Carrier should not hold lock");
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ThreadInfo.getLockOwnerId and getLockInfo return information about a
|
||||
* synthetic lock that make it appear that the carrier is blocking waiting on the
|
||||
* virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testCarrierThreadWaits() throws Exception {
|
||||
ThreadFactory factory = Executors.defaultThreadFactory();
|
||||
try (var scheduler = new CustomScheduler(factory)) {
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
Thread vthread = scheduler.forkVirtualThread(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
try {
|
||||
awaitTrue(started);
|
||||
|
||||
long carrierId = scheduler.carrier().threadId();
|
||||
long vthreadId = vthread.threadId();
|
||||
|
||||
ThreadInfo threadInfo = ManagementFactory.getThreadMXBean().getThreadInfo(carrierId);
|
||||
assertNotNull(threadInfo);
|
||||
|
||||
// carrier should be blocked waiting for lock owned by virtual thread
|
||||
assertEquals(vthreadId, threadInfo.getLockOwnerId());
|
||||
|
||||
// carrier thread should be on blocked waiting on virtual thread
|
||||
LockInfo lockInfo = threadInfo.getLockInfo();
|
||||
assertNotNull(lockInfo);
|
||||
assertEquals(vthread.getClass().getName(), lockInfo.getClassName());
|
||||
assertEquals(System.identityHashCode(vthread), lockInfo.getIdentityHashCode());
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom scheduler with a single carrier thread.
|
||||
*/
|
||||
private static class CustomScheduler implements AutoCloseable {
|
||||
private final ExecutorService pool;
|
||||
private final Executor scheduler;
|
||||
private final AtomicReference<Thread> carrierRef = new AtomicReference<>();
|
||||
|
||||
CustomScheduler(ThreadFactory factory) {
|
||||
pool = Executors.newSingleThreadExecutor(factory);
|
||||
scheduler = task -> {
|
||||
pool.submit(() -> {
|
||||
carrierRef.set(Thread.currentThread());
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
carrierRef.set(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the carrier thread if a virtual thread is mounted.
|
||||
*/
|
||||
Thread carrier() throws InterruptedException {
|
||||
return carrierRef.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a virtual thread to execute the give task.
|
||||
*/
|
||||
Thread forkVirtualThread(Runnable task) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(task);
|
||||
thread.start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
test/jdk/java/lang/Thread/virtual/Collectable.java
Normal file
69
test/jdk/java/lang/Thread/virtual/Collectable.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test that virtual threads are GC'ed
|
||||
* @run junit Collectable
|
||||
*/
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class Collectable {
|
||||
|
||||
/**
|
||||
* Test that an unstarted virtual thread can be GC"ed.
|
||||
*/
|
||||
@Test
|
||||
void testUnstartedThread() {
|
||||
var thread = Thread.ofVirtual().unstarted(() -> { });
|
||||
var ref = new WeakReference<>(thread);
|
||||
thread = null;
|
||||
waitUntilCleared(ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a terminated virtual thread can be GC'ed.
|
||||
*/
|
||||
@Test
|
||||
void testTerminatedThread() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> { });
|
||||
thread.join();
|
||||
var ref = new WeakReference<>(thread);
|
||||
thread = null;
|
||||
waitUntilCleared(ref);
|
||||
}
|
||||
|
||||
private static void waitUntilCleared(WeakReference<?> ref) {
|
||||
while (ref.get() != null) {
|
||||
System.gc();
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignore) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
286
test/jdk/java/lang/Thread/virtual/CustomScheduler.java
Normal file
286
test/jdk/java/lang/Thread/virtual/CustomScheduler.java
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test virtual threads using a custom scheduler
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit CustomScheduler
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class CustomScheduler {
|
||||
private static ExecutorService scheduler1;
|
||||
private static ExecutorService scheduler2;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
scheduler1 = Executors.newFixedThreadPool(1);
|
||||
scheduler2 = Executors.newFixedThreadPool(1);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void shutdown() {
|
||||
scheduler1.shutdown();
|
||||
scheduler2.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test platform thread creating a virtual thread that uses a custom scheduler.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler1() throws Exception {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
Thread thread = factory.newThread(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread creating a virtual thread that uses a custom scheduler.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler2() throws Exception {
|
||||
VThreadRunner.run(this::testCustomScheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread using custom scheduler creating a virtual thread.
|
||||
* The scheduler should be inherited.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler3() throws Exception {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
Thread thread = factory.newThread(() -> {
|
||||
try {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
}).join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
thread.join();
|
||||
assertTrue(ref.get() == scheduler1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread using custom scheduler creating a virtual thread
|
||||
* that uses a different custom scheduler.
|
||||
*/
|
||||
@Test
|
||||
void testCustomScheduler4() throws Exception {
|
||||
var ref = new AtomicReference<Executor>();
|
||||
ThreadFactory factory1 = VThreadScheduler.virtualThreadFactory(scheduler1);
|
||||
ThreadFactory factory2 = VThreadScheduler.virtualThreadFactory(scheduler2);
|
||||
Thread thread1 = factory1.newThread(() -> {
|
||||
try {
|
||||
Thread thread2 = factory2.newThread(() -> {
|
||||
ref.set(VThreadScheduler.scheduler(Thread.currentThread()));
|
||||
});
|
||||
thread2.start();
|
||||
thread2.join();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread1.start();
|
||||
thread1.join();
|
||||
assertTrue(ref.get() == scheduler2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test running task on a virtual thread, should thrown WrongThreadException.
|
||||
*/
|
||||
@Test
|
||||
void testBadCarrier() {
|
||||
Executor scheduler = (task) -> {
|
||||
var exc = new AtomicReference<Throwable>();
|
||||
try {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
task.run();
|
||||
fail();
|
||||
} catch (Throwable e) {
|
||||
exc.set(e);
|
||||
}
|
||||
}).join();
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
assertTrue(exc.get() instanceof WrongThreadException);
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test parking with the virtual thread interrupt set, should not leak to the
|
||||
* carrier thread when the task completes.
|
||||
*/
|
||||
@Test
|
||||
void testParkWithInterruptSet() {
|
||||
Thread carrier = Thread.currentThread();
|
||||
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
|
||||
try {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
Thread.yield();
|
||||
});
|
||||
vthread.start();
|
||||
assertTrue(vthread.isInterrupted());
|
||||
assertFalse(carrier.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test terminating with the virtual thread interrupt set, should not leak to
|
||||
* the carrier thread when the task completes.
|
||||
*/
|
||||
@Test
|
||||
void testTerminateWithInterruptSet() {
|
||||
Thread carrier = Thread.currentThread();
|
||||
assumeFalse(carrier.isVirtual(), "Main thread is a virtual thread");
|
||||
try {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(Runnable::run);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
Thread.currentThread().interrupt();
|
||||
});
|
||||
vthread.start();
|
||||
assertTrue(vthread.isInterrupted());
|
||||
assertFalse(carrier.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test running task with the carrier's interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testRunWithInterruptSet() throws Exception {
|
||||
assumeFalse(Thread.currentThread().isVirtual(), "Main thread is a virtual thread");
|
||||
Executor scheduler = (task) -> {
|
||||
Thread.currentThread().interrupt();
|
||||
task.run();
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
try {
|
||||
AtomicBoolean interrupted = new AtomicBoolean();
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
interrupted.set(Thread.currentThread().isInterrupted());
|
||||
});
|
||||
vthread.start();
|
||||
assertFalse(vthread.isInterrupted());
|
||||
} finally {
|
||||
Thread.interrupted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom scheduler throwing OOME when starting a thread.
|
||||
*/
|
||||
@Test
|
||||
void testThreadStartOOME() throws Exception {
|
||||
Executor scheduler = task -> {
|
||||
System.err.println("OutOfMemoryError");
|
||||
throw new OutOfMemoryError();
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
Thread thread = factory.newThread(() -> { });
|
||||
assertThrows(OutOfMemoryError.class, thread::start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom scheduler throwing OOME when unparking a thread.
|
||||
*/
|
||||
@Test
|
||||
void testThreadUnparkOOME() throws Exception {
|
||||
try (ExecutorService executor = Executors.newFixedThreadPool(1)) {
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Executor scheduler = task -> {
|
||||
switch (counter.getAndIncrement()) {
|
||||
case 0 -> executor.execute(task); // Thread.start
|
||||
case 1, 2 -> { // unpark attempt 1+2
|
||||
System.err.println("OutOfMemoryError");
|
||||
throw new OutOfMemoryError();
|
||||
}
|
||||
default -> executor.execute(task);
|
||||
}
|
||||
executor.execute(task);
|
||||
};
|
||||
|
||||
// start thread and wait for it to park
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
var thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// unpark thread, this should retry until OOME is not thrown
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
476
test/jdk/java/lang/Thread/virtual/JfrEvents.java
Normal file
476
test/jdk/java/lang/Thread/virtual/JfrEvents.java
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Basic test for JFR jdk.VirtualThreadXXX events
|
||||
* @requires vm.continuations & vm.hasJFR
|
||||
* @modules jdk.jfr java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native --enable-native-access=ALL-UNNAMED JfrEvents
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.jfr.EventType;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class JfrEvents {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need at least two carriers to test pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadStart and jdk.VirtualThreadEnd events.
|
||||
*/
|
||||
@Test
|
||||
void testVirtualThreadStartAndEnd() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadStart");
|
||||
recording.enable("jdk.VirtualThreadEnd");
|
||||
|
||||
// execute 100 tasks, each in their own virtual thread
|
||||
recording.start();
|
||||
try {
|
||||
List<Thread> threads = IntStream.range(0, 100)
|
||||
.mapToObj(_ -> Thread.startVirtualThread(() -> { }))
|
||||
.toList();
|
||||
for (Thread t : threads) {
|
||||
t.join();
|
||||
}
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
Map<String, Integer> events = sumEvents(recording);
|
||||
System.err.println(events);
|
||||
|
||||
int startCount = events.getOrDefault("jdk.VirtualThreadStart", 0);
|
||||
int endCount = events.getOrDefault("jdk.VirtualThreadEnd", 0);
|
||||
assertEquals(100, startCount);
|
||||
assertEquals(100, endCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when parking while pinned.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testParkWhenPinned(boolean timed) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
while (!done.get()) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and park
|
||||
awaitTrue(started);
|
||||
await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when blocking on monitor while pinned.
|
||||
*/
|
||||
@Test
|
||||
void testBlockWhenPinned() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Object lock = new Object();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
synchronized (lock) { }
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
awaitTrue(started);
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when waiting with Object.wait while pinned.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitWhenPinned(boolean timed) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Object lock = new Object();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.set(true);
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (timed) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(started);
|
||||
await(vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
} finally {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when parking in a class initializer.
|
||||
*/
|
||||
@Test
|
||||
void testParkInClassInitializer() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
LockSupport.park();
|
||||
}
|
||||
static void m() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
Thread vthread = Thread.startVirtualThread(() -> {
|
||||
started.set(true);
|
||||
TestClass.m();
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for it to start and park
|
||||
awaitTrue(started);
|
||||
await(vthread, Thread.State.WAITING);
|
||||
} finally {
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when blocking on monitor in a class initializer.
|
||||
*/
|
||||
@Test
|
||||
void testBlockInClassInitializer() throws Exception {
|
||||
class LockHolder {
|
||||
static final Object lock = new Object();
|
||||
}
|
||||
class TestClass {
|
||||
static {
|
||||
synchronized (LockHolder.lock) { }
|
||||
}
|
||||
static void m() {
|
||||
// no nothing
|
||||
}
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started = new AtomicBoolean();
|
||||
Thread vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.set(true);
|
||||
TestClass.m();
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized (LockHolder.lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
awaitTrue(started);
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
assertContainsPinnedEvent(recording, vthread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned event when waiting for a class initializer while pinned.
|
||||
*/
|
||||
@Test
|
||||
void testWaitingForClassInitializerWhenPinned() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
LockSupport.park();
|
||||
}
|
||||
static void m() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
var started1 = new AtomicBoolean();
|
||||
var started2 = new AtomicBoolean();
|
||||
|
||||
Thread vthread1 = Thread.ofVirtual().unstarted(() -> {
|
||||
started1.set(true);
|
||||
TestClass.m();
|
||||
});
|
||||
Thread vthread2 = Thread.ofVirtual().unstarted(() -> {
|
||||
started2.set(true);
|
||||
VThreadPinner.runPinned(() -> {
|
||||
TestClass.m();
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
// start first virtual thread and wait for it to start + park
|
||||
vthread1.start();
|
||||
awaitTrue(started1);
|
||||
await(vthread1, Thread.State.WAITING);
|
||||
|
||||
// start second virtual thread and wait for it to start
|
||||
vthread2.start();
|
||||
awaitTrue(started2);
|
||||
|
||||
// give time for second virtual thread to wait in VM
|
||||
Thread.sleep(3000);
|
||||
|
||||
} finally {
|
||||
LockSupport.unpark(vthread1);
|
||||
vthread1.join();
|
||||
vthread2.join();
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// the recording should have a pinned event for vthread2
|
||||
assertContainsPinnedEvent(recording, vthread2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadSubmitFailed event.
|
||||
*/
|
||||
@Test
|
||||
void testVirtualThreadSubmitFailed() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadSubmitFailed");
|
||||
|
||||
recording.start();
|
||||
try (ExecutorService pool = Executors.newCachedThreadPool()) {
|
||||
Executor scheduler = task -> pool.execute(task);
|
||||
|
||||
// create virtual thread that uses custom scheduler
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
// start a thread
|
||||
Thread thread = factory.newThread(LockSupport::park);
|
||||
thread.start();
|
||||
|
||||
// wait for thread to park
|
||||
await(thread, Thread.State.WAITING);
|
||||
|
||||
// shutdown scheduler
|
||||
pool.shutdown();
|
||||
|
||||
// unpark, the submit should fail
|
||||
try {
|
||||
LockSupport.unpark(thread);
|
||||
fail();
|
||||
} catch (RejectedExecutionException expected) { }
|
||||
|
||||
// start another thread, it should fail and an event should be recorded
|
||||
try {
|
||||
factory.newThread(LockSupport::park).start();
|
||||
throw new RuntimeException("RejectedExecutionException expected");
|
||||
} catch (RejectedExecutionException expected) { }
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
List<RecordedEvent> submitFailedEvents = find(recording, "jdk.VirtualThreadSubmitFailed");
|
||||
System.err.println(submitFailedEvents);
|
||||
assertTrue(submitFailedEvents.size() == 2, "Expected two events");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of events in the given recording with the given name.
|
||||
*/
|
||||
private static List<RecordedEvent> find(Recording recording, String name) throws IOException {
|
||||
Path recordingFile = recordingFile(recording);
|
||||
return RecordingFile.readAllEvents(recordingFile)
|
||||
.stream()
|
||||
.filter(e -> e.getEventType().getName().equals(name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the events from the recording and return a map of event name to count.
|
||||
*/
|
||||
private static Map<String, Integer> sumEvents(Recording recording) throws IOException {
|
||||
Path recordingFile = recordingFile(recording);
|
||||
List<RecordedEvent> events = RecordingFile.readAllEvents(recordingFile);
|
||||
return events.stream()
|
||||
.map(RecordedEvent::getEventType)
|
||||
.collect(Collectors.groupingBy(EventType::getName,
|
||||
Collectors.summingInt(x -> 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to the recording file.
|
||||
*/
|
||||
private static Path recordingFile(Recording recording) throws IOException {
|
||||
Path recordingFile = recording.getDestination();
|
||||
if (recordingFile == null) {
|
||||
ProcessHandle h = ProcessHandle.current();
|
||||
recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
|
||||
recording.dump(recordingFile);
|
||||
}
|
||||
return recordingFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a recording contains a jdk.VirtualThreadPinned event on the given thread.
|
||||
*/
|
||||
private void assertContainsPinnedEvent(Recording recording, Thread thread) throws IOException {
|
||||
List<RecordedEvent> pinnedEvents = find(recording, "jdk.VirtualThreadPinned");
|
||||
assertTrue(pinnedEvents.size() > 0, "No jdk.VirtualThreadPinned events in recording");
|
||||
System.err.println(pinnedEvents);
|
||||
|
||||
long tid = thread.threadId();
|
||||
assertTrue(pinnedEvents.stream()
|
||||
.anyMatch(e -> e.getThread().getJavaThreadId() == tid),
|
||||
"jdk.VirtualThreadPinned for javaThreadId = " + tid + " not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given boolean to be set to true.
|
||||
*/
|
||||
private void awaitTrue(AtomicBoolean b) throws InterruptedException {
|
||||
while (!b.get()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
490
test/jdk/java/lang/Thread/virtual/KlassInit.java
Normal file
490
test/jdk/java/lang/Thread/virtual/KlassInit.java
Normal file
|
|
@ -0,0 +1,490 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xint -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit*
|
||||
* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=gc
|
||||
* @library /test/lib
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @run junit/othervm/timeout=480 -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000
|
||||
* -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run
|
||||
* -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class KlassInit {
|
||||
private static final int MAX_VTHREAD_COUNT = 8 * Runtime.getRuntime().availableProcessors();
|
||||
|
||||
private static final CountDownLatch finishInvokeStatic1 = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishInvokeStatic2 = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishInvokeStatic3 = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishNew = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishGetStatic = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishPutStatic = new CountDownLatch(1);
|
||||
private static final CountDownLatch finishFailedInit = new CountDownLatch(1);
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on invokestatic bytecode release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitInvokeStatic1() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishInvokeStatic1.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass.m();
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishInvokeStatic1.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with static method that takes arguments.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitInvokeStatic2() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishInvokeStatic2.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m(ArrayList<String> list, int id) {
|
||||
String str = list.get(0);
|
||||
if (str != null && str.equals("VThread#" + id)) {
|
||||
list.add("Success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
ArrayList<String>[] lists = new ArrayList[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
lists[i] = new ArrayList<>(List.of("VThread#" + i));
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass.m(lists[id], id);
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
System.gc();
|
||||
finishInvokeStatic2.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
assertEquals(lists[i].get(1), "Success");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invokestatic as first bytecode in method.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitInvokeStatic3() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishInvokeStatic3.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {
|
||||
}
|
||||
}
|
||||
class Driver {
|
||||
static void foo() {
|
||||
TestClass.m();
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
started[0] = new CountDownLatch(1);
|
||||
vthreads[0] = Thread.ofVirtual().start(() -> {
|
||||
started[0].countDown();
|
||||
TestClass.m();
|
||||
});
|
||||
started[0].await();
|
||||
await(vthreads[0], Thread.State.WAITING);
|
||||
|
||||
for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
Driver.foo();
|
||||
});
|
||||
}
|
||||
for (int i = 1; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishInvokeStatic3.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on new bytecode release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitNew() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishNew.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
void m() {
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass x = new TestClass();
|
||||
x.m();
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishNew.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on getstatic bytecode release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitGetStatic() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishGetStatic.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
public static int NUMBER = 150;
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
AtomicInteger[] result = new AtomicInteger[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
result[i] = new AtomicInteger();
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
result[id].set(TestClass.NUMBER);
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishGetStatic.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
assertEquals(result[i].get(), TestClass.NUMBER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that threads blocked waiting for klass to be initialized
|
||||
* on putstatic release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitPutStatic() throws Exception {
|
||||
class TestClass {
|
||||
static {
|
||||
try {
|
||||
finishPutStatic.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
public static int NUMBER;
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
TestClass.NUMBER = id;
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishPutStatic.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that interruptions during preemption on klass init
|
||||
* are preserved.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("interruptTestCases")
|
||||
void testReleaseAtKlassInitPreserverInterrupt(int timeout, Runnable m, CountDownLatch finish) throws Exception {
|
||||
// Start vthread1 and wait until it blocks in TestClassX initializer
|
||||
var vthread1_started = new CountDownLatch(1);
|
||||
var vthread1 = Thread.ofVirtual().start(() -> {
|
||||
vthread1_started.countDown();
|
||||
m.run();
|
||||
});
|
||||
vthread1_started.await();
|
||||
await(vthread1, Thread.State.WAITING);
|
||||
|
||||
// Start vthread2 and wait until it gets preempted on TestClassX initialization
|
||||
var lock = new Object();
|
||||
var interruptedException = new AtomicBoolean();
|
||||
var vthread2_started = new CountDownLatch(1);
|
||||
var vthread2 = Thread.ofVirtual().start(() -> {
|
||||
vthread2_started.countDown();
|
||||
m.run();
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// check stack trace has the expected frames
|
||||
Set<String> expected = Set.of("wait0", "wait", "run");
|
||||
Set<String> methods = Stream.of(e.getStackTrace())
|
||||
.map(StackTraceElement::getMethodName)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(methods.containsAll(expected));
|
||||
interruptedException.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
vthread2_started.await();
|
||||
await(vthread2, Thread.State.WAITING);
|
||||
|
||||
// Interrupt vthread2 and let initialization of TestClassX finish
|
||||
vthread2.interrupt();
|
||||
finish.countDown();
|
||||
vthread1.join();
|
||||
vthread2.join();
|
||||
assertTrue(interruptedException.get());
|
||||
}
|
||||
|
||||
static CountDownLatch finishInterrupt0 = new CountDownLatch(1);
|
||||
class TestClass0 {
|
||||
static {
|
||||
try {
|
||||
finishInterrupt0.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {}
|
||||
}
|
||||
|
||||
static CountDownLatch finishInterrupt30000 = new CountDownLatch(1);
|
||||
class TestClass30000 {
|
||||
static {
|
||||
try {
|
||||
finishInterrupt30000.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {}
|
||||
}
|
||||
|
||||
static CountDownLatch finishInterruptMax = new CountDownLatch(1);
|
||||
class TestClassMax {
|
||||
static {
|
||||
try {
|
||||
finishInterruptMax.await();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {}
|
||||
}
|
||||
|
||||
static Stream<Arguments> interruptTestCases() {
|
||||
return Stream.of(
|
||||
Arguments.of(0, (Runnable) TestClass0::m, finishInterrupt0),
|
||||
Arguments.of(30000, (Runnable) TestClass30000::m, finishInterrupt30000),
|
||||
Arguments.of(Integer.MAX_VALUE, (Runnable) TestClassMax::m, finishInterruptMax)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case of threads blocked waiting for klass to be initialized
|
||||
* when the klass initialization fails.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseAtKlassInitFailedInit() throws Exception {
|
||||
class TestClass {
|
||||
static int[] a = {1, 2, 3};
|
||||
static {
|
||||
try {
|
||||
finishFailedInit.await();
|
||||
a[3] = 4;
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
static void m() {
|
||||
}
|
||||
}
|
||||
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
|
||||
AtomicInteger failedCount = new AtomicInteger();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
final int id = i;
|
||||
started[i] = new CountDownLatch(1);
|
||||
vthreads[i] = Thread.ofVirtual().start(() -> {
|
||||
started[id].countDown();
|
||||
try {
|
||||
TestClass.m();
|
||||
} catch (NoClassDefFoundError e) {
|
||||
failedCount.getAndIncrement();
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
failedCount.getAndIncrement();
|
||||
}
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
started[i].await();
|
||||
await(vthreads[i], Thread.State.WAITING);
|
||||
}
|
||||
|
||||
finishFailedInit.countDown();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
assertEquals(MAX_VTHREAD_COUNT, failedCount.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
217
test/jdk/java/lang/Thread/virtual/Locking.java
Normal file
217
test/jdk/java/lang/Thread/virtual/Locking.java
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test virtual threads using java.util.concurrent locks
|
||||
* @library /test/lib
|
||||
* @run junit Locking
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class Locking {
|
||||
|
||||
/**
|
||||
* Test lock/unlock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
lock.lock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
lock.unlock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test tryLock/unlock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
boolean acquired = lock.tryLock();
|
||||
assertTrue(acquired);
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
lock.unlock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lock/lock/unlock/unlock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 0);
|
||||
lock.lock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 1);
|
||||
lock.lock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 2);
|
||||
lock.unlock();
|
||||
assertTrue(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 1);
|
||||
lock.unlock();
|
||||
assertFalse(lock.isHeldByCurrentThread());
|
||||
assertTrue(lock.getHoldCount() == 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked by platform thread, virtual thread tries to lock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock4() throws Exception {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
var holdsLock = new AtomicBoolean();
|
||||
|
||||
Thread thread;
|
||||
|
||||
// thread acquires lock
|
||||
lock.lock();
|
||||
try {
|
||||
thread = Thread.ofVirtual().start(() -> {
|
||||
lock.lock(); // should block
|
||||
holdsLock.set(true);
|
||||
LockSupport.park();
|
||||
lock.unlock();
|
||||
holdsLock.set(false);
|
||||
});
|
||||
// give time for virtual thread to block
|
||||
Thread.sleep(500);
|
||||
assertFalse(holdsLock.get());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
// virtual thread should acquire lock, park, unpark, and then release lock
|
||||
while (!holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
LockSupport.unpark(thread);
|
||||
while (holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked by virtual thread, platform thread tries to lock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock5() throws Exception {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
LockSupport.park();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
// wat for virtual thread to acquire lock
|
||||
while (!lock.isLocked()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
// thread cannot acquire lock
|
||||
try {
|
||||
assertFalse(lock.tryLock());
|
||||
} finally {
|
||||
// virtual thread should unlock
|
||||
LockSupport.unpark(thread);
|
||||
|
||||
// thread should be able to acquire lock
|
||||
lock.lock();
|
||||
lock.unlock();
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test locked by virtual thread, another virtual thread tries to lock.
|
||||
*/
|
||||
@Test
|
||||
void testReentrantLock6() throws Exception {
|
||||
ReentrantLock lock = new ReentrantLock();
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
lock.lock();
|
||||
try {
|
||||
LockSupport.park();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
// wat for virtual thread to acquire lock
|
||||
while (!lock.isLocked()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
|
||||
var holdsLock = new AtomicBoolean();
|
||||
var thread2 = Thread.ofVirtual().start(() -> {
|
||||
lock.lock();
|
||||
holdsLock.set(true);
|
||||
LockSupport.park();
|
||||
lock.unlock();
|
||||
holdsLock.set(false);
|
||||
});
|
||||
|
||||
// virtual thread2 should block
|
||||
Thread.sleep(1000);
|
||||
assertFalse(holdsLock.get());
|
||||
|
||||
// unpark virtual thread1
|
||||
LockSupport.unpark(thread1);
|
||||
|
||||
// virtual thread2 should acquire lock
|
||||
while (!holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
// unpark virtual thread and it should release lock
|
||||
LockSupport.unpark(thread2);
|
||||
while (holdsLock.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
}
|
||||
509
test/jdk/java/lang/Thread/virtual/MiscMonitorTests.java
Normal file
509
test/jdk/java/lang/Thread/virtual/MiscMonitorTests.java
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Tests for object monitors that have been useful to find bugs
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xint MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xcomp MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel3
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:TieredStopAtLevel=3 MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @summary Test virtual threads using synchronized
|
||||
* @library /test/lib
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation MiscMonitorTests
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=gc
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @library /test/lib
|
||||
* @modules java.base/java.lang:+open
|
||||
* @run junit/othervm/timeout=480 -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 MiscMonitorTests
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MiscMonitorTests {
|
||||
static final int CARRIER_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* Test that yielding while holding monitors releases carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseOnYield() throws Exception {
|
||||
try (var test = new TestReleaseOnYield()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestReleaseOnYield extends TestBase {
|
||||
final Object lock = new Object();
|
||||
volatile boolean finish;
|
||||
volatile int counter;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT;
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "Batch1");
|
||||
sleep(500); // Give time for threads to reach Thread.yield
|
||||
startVThreads(() -> bar(), vthreadCount, "Batch2");
|
||||
|
||||
while (counter != vthreadCount) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
while (!finish) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
void bar() {
|
||||
synchronized (lock) {
|
||||
counter++;
|
||||
}
|
||||
System.err.println("Exiting bar from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test yielding while holding monitors with recursive locking releases carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseOnYieldRecursive() throws Exception {
|
||||
try (var test = new TestReleaseOnYieldRecursive()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestReleaseOnYieldRecursive extends TestBase {
|
||||
final Object lock = new Object();
|
||||
volatile boolean finish;
|
||||
volatile int counter;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT;
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "Batch1");
|
||||
sleep(500); // Give time for threads to reach Thread.yield
|
||||
startVThreads(() -> bar(), vthreadCount, "Batch2");
|
||||
|
||||
while (counter != 2 * vthreadCount) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
while (!finish) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
void bar() {
|
||||
synchronized (lock) {
|
||||
counter++;
|
||||
}
|
||||
recursive(10);
|
||||
System.err.println("Exiting bar from thread " + Thread.currentThread().getName());
|
||||
};
|
||||
|
||||
void recursive(int count) {
|
||||
synchronized (Thread.currentThread()) {
|
||||
if (count > 0) {
|
||||
recursive(count - 1);
|
||||
} else {
|
||||
synchronized (lock) {
|
||||
counter++;
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that contention on monitorenter releases carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseOnContention() throws Exception {
|
||||
try (var test = new TestReleaseOnContention()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestReleaseOnContention extends TestBase {
|
||||
final Object lock = new Object();
|
||||
volatile boolean finish;
|
||||
volatile int counter;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
sleep(500); // Give time for threads to reach synchronized (lock)
|
||||
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
synchronized (lock) {
|
||||
while (!finish) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contention on monitorenter with extra monitors on stack shared by all threads.
|
||||
*/
|
||||
@Test
|
||||
void testContentionMultipleMonitors() throws Exception {
|
||||
try (var test = new TestContentionMultipleMonitors()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestContentionMultipleMonitors extends TestBase {
|
||||
static int MONITOR_COUNT = 12;
|
||||
final Object[] lockArray = new Object[MONITOR_COUNT];
|
||||
final AtomicInteger workerCount = new AtomicInteger(0);
|
||||
volatile boolean finish;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
|
||||
sleep(5000);
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
assertEquals(vthreadCount, workerCount.get());
|
||||
}
|
||||
|
||||
void foo() {
|
||||
while (!finish) {
|
||||
int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
recursive1(lockNumber, lockNumber);
|
||||
}
|
||||
}
|
||||
workerCount.getAndIncrement();
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
public void recursive1(int depth, int lockNumber) {
|
||||
if (depth > 0) {
|
||||
recursive1(depth - 1, lockNumber);
|
||||
} else {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
}
|
||||
recursive2(lockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public void recursive2(int lockNumber) {
|
||||
if (lockNumber + 2 <= MONITOR_COUNT - 1) {
|
||||
lockNumber += 2;
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
Thread.yield();
|
||||
recursive2(lockNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contention on monitorenter with extra monitors on stack both local only and shared by all threads.
|
||||
*/
|
||||
@Test
|
||||
void testContentionMultipleMonitors2() throws Exception {
|
||||
try (var test = new TestContentionMultipleMonitors2()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestContentionMultipleMonitors2 extends TestBase {
|
||||
int MONITOR_COUNT = 12;
|
||||
final Object[] lockArray = new Object[MONITOR_COUNT];
|
||||
final AtomicInteger workerCount = new AtomicInteger(0);
|
||||
volatile boolean finish;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
|
||||
sleep(5000);
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
assertEquals(vthreadCount, workerCount.get());
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object[] myLockArray = new Object[MONITOR_COUNT];
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
myLockArray[i] = new Object();
|
||||
}
|
||||
|
||||
while (!finish) {
|
||||
int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
|
||||
synchronized (myLockArray[lockNumber]) {
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
recursive1(lockNumber, lockNumber, myLockArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
workerCount.getAndIncrement();
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
public void recursive1(int depth, int lockNumber, Object[] myLockArray) {
|
||||
if (depth > 0) {
|
||||
recursive1(depth - 1, lockNumber, myLockArray);
|
||||
} else {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
}
|
||||
recursive2(lockNumber, myLockArray);
|
||||
}
|
||||
}
|
||||
|
||||
public void recursive2(int lockNumber, Object[] myLockArray) {
|
||||
if (lockNumber + 2 <= MONITOR_COUNT - 1) {
|
||||
lockNumber += 2;
|
||||
synchronized (myLockArray[lockNumber]) {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
}
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
Thread.yield();
|
||||
recursive2(lockNumber, myLockArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test contention on monitorenter with synchronized methods.
|
||||
*/
|
||||
@Test
|
||||
void testContentionWithSyncMethods() throws Exception {
|
||||
try (var test = new TestContentionWithSyncMethods()) {
|
||||
test.runTest();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestContentionWithSyncMethods extends TestBase {
|
||||
static final int MONITOR_COUNT = 12;
|
||||
final Object[] lockArray = new Object[MONITOR_COUNT];
|
||||
final AtomicInteger workerCount = new AtomicInteger(0);
|
||||
volatile boolean finish;
|
||||
|
||||
@Override
|
||||
void runTest() throws Exception {
|
||||
int vthreadCount = CARRIER_COUNT * 8;
|
||||
for (int i = 0; i < MONITOR_COUNT; i++) {
|
||||
lockArray[i] = new Object();
|
||||
}
|
||||
|
||||
startVThreads(() -> foo(), vthreadCount, "VThread");
|
||||
|
||||
sleep(5000);
|
||||
finish = true;
|
||||
joinVThreads();
|
||||
assertEquals(vthreadCount, workerCount.get());
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object myLock = new Object();
|
||||
|
||||
while (!finish) {
|
||||
int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
|
||||
synchronized (myLock) {
|
||||
synchronized (lockArray[lockNumber]) {
|
||||
recursive(lockNumber, myLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
workerCount.getAndIncrement();
|
||||
System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
|
||||
};
|
||||
|
||||
synchronized void recursive(int depth, Object myLock) {
|
||||
if (depth > 0) {
|
||||
recursive(depth - 1, myLock);
|
||||
} else {
|
||||
if (Math.random() < 0.5) {
|
||||
Thread.yield();
|
||||
} else {
|
||||
synchronized (myLock) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test wait/notify mechanism.
|
||||
*/
|
||||
@Test
|
||||
void waitNotifyTest() throws Exception {
|
||||
int threadCount = 1000;
|
||||
int waitTime = 50;
|
||||
Thread[] vthread = new Thread[threadCount];
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (System.currentTimeMillis() - start < 5000) {
|
||||
CountDownLatch latchStart = new CountDownLatch(threadCount);
|
||||
CountDownLatch latchFinish = new CountDownLatch(threadCount);
|
||||
Object object = new Object();
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
vthread[i] = Thread.ofVirtual().start(() -> {
|
||||
synchronized (object) {
|
||||
try {
|
||||
latchStart.countDown();
|
||||
object.wait(waitTime);
|
||||
} catch (InterruptedException e) {
|
||||
//do nothing;
|
||||
}
|
||||
}
|
||||
latchFinish.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
latchStart.await();
|
||||
synchronized (object) {
|
||||
object.notifyAll();
|
||||
}
|
||||
latchFinish.await();
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
vthread[i].join();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
//do nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class TestBase implements AutoCloseable {
|
||||
final ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT);
|
||||
final List<Thread[]> vthreadList = new ArrayList<>();
|
||||
|
||||
abstract void runTest() throws Exception;
|
||||
|
||||
void startVThreads(Runnable r, int count, String name) {
|
||||
Thread vthreads[] = new Thread[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
vthreads[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name(name + "-" + i).start(r);
|
||||
}
|
||||
vthreadList.add(vthreads);
|
||||
}
|
||||
|
||||
void joinVThreads() throws Exception {
|
||||
for (Thread[] vthreads : vthreadList) {
|
||||
for (Thread vthread : vthreads) {
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sleep(long ms) throws Exception {
|
||||
Thread.sleep(ms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
scheduler.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
547
test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
Normal file
547
test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java
Normal file
|
|
@ -0,0 +1,547 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual thread with monitor enter/exit
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xint --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:-TieredCompilation --enable-native-access=ALL-UNNAMED MonitorEnterExit
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class MonitorEnterExit {
|
||||
static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
|
||||
static final int MAX_ENTER_DEPTH = 256;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for tests that pin
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with no contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnterNoContention() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with contention, monitor is held by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWhenHeldByPlatformThread() throws Exception {
|
||||
testEnterWithContention();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with contention, monitor is held by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWhenHeldByVirtualThread() throws Exception {
|
||||
VThreadRunner.run(this::testEnterWithContention);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter with contention, monitor will be held by caller thread.
|
||||
*/
|
||||
private void testEnterWithContention() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor reenter.
|
||||
*/
|
||||
@Test
|
||||
void testReenter() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
testReenter(lock, 0);
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
private void testReenter(Object lock, int depth) {
|
||||
if (depth < MAX_ENTER_DEPTH) {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
testReenter(lock, depth + 1);
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor reenter when there are other threads blocked trying to enter.
|
||||
*/
|
||||
@Test
|
||||
void testReenterWithContention() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
testReenter(lock, 0, threads);
|
||||
|
||||
// wait for threads to terminate
|
||||
for (Thread vthread : threads) {
|
||||
vthread.join();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
|
||||
if (depth < MAX_ENTER_DEPTH) {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
|
||||
// start platform or virtual thread that blocks waiting to enter
|
||||
var started = new CountDownLatch(1);
|
||||
ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
|
||||
? Thread.ofPlatform().factory()
|
||||
: Thread.ofVirtual().factory();
|
||||
var thread = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
/* do nothing */
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
threads.add(thread);
|
||||
|
||||
// test reenter
|
||||
testReenter(lock, depth + 1, threads);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor enter when pinned.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWhenPinned() throws Exception {
|
||||
var lock = new Object();
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test monitor reenter when pinned.
|
||||
*/
|
||||
@Test
|
||||
void testReenterWhenPinned() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var lock = new Object();
|
||||
synchronized (lock) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contended monitor enter when pinned. Monitor is held by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
|
||||
testEnterWithContentionWhenPinned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contended monitor enter when pinned. Monitor is held by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
|
||||
VThreadRunner.run(this::testEnterWithContentionWhenPinned);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test contended monitor enter when pinned, monitor will be held by caller thread.
|
||||
*/
|
||||
private void testEnterWithContentionWhenPinned() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
Thread vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
entered.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
synchronized (lock) {
|
||||
// start thread and wait for it to block
|
||||
vthread.start();
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
vthread.join();
|
||||
|
||||
// check thread entered monitor
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that blocking waiting to enter a monitor releases the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testReleaseWhenBlocked() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = new Object();
|
||||
|
||||
// thread enters monitor
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
synchronized (lock) {
|
||||
// start thread and wait for it to block
|
||||
vthread1.start();
|
||||
started.await();
|
||||
await(vthread1, Thread.State.BLOCKED);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
}
|
||||
} finally {
|
||||
vthread1.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lots of virtual threads blocked waiting to enter a monitor. If the number
|
||||
* of virtual threads exceeds the number of carrier threads this test will hang if
|
||||
* carriers aren't released.
|
||||
*/
|
||||
@Test
|
||||
void testManyBlockedThreads() throws Exception {
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
var lock = new Object();
|
||||
synchronized (lock) {
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
}
|
||||
});
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
vthreads[i] = vthread;
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of elements that are ordered pairs of platform and virtual thread
|
||||
* counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
|
||||
*/
|
||||
static Stream<Arguments> threadCounts() {
|
||||
return IntStream.range(0, 17)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(i -> i)
|
||||
.flatMap(np -> IntStream.range(2, 33)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(vp -> Arguments.of(np, vp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test mutual exclusion of monitors with platform and virtual threads.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
class Counter {
|
||||
int count;
|
||||
synchronized void increment() {
|
||||
count++;
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
var counter = new Counter();
|
||||
int nThreads = nPlatformThreads + nVirtualThreads;
|
||||
var threads = new Thread[nThreads];
|
||||
int index = 0;
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads[index] = Thread.ofPlatform()
|
||||
.name("platform-" + index)
|
||||
.unstarted(counter::increment);
|
||||
index++;
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads[index] = Thread.ofVirtual()
|
||||
.name("virtual-" + index)
|
||||
.unstarted(counter::increment);
|
||||
index++;
|
||||
}
|
||||
// start all threads
|
||||
for (Thread thread : threads) {
|
||||
thread.start();
|
||||
}
|
||||
// wait for all threads to terminate
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
assertEquals(nThreads, counter.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unblocking a virtual thread waiting to enter a monitor held by a platform thread.
|
||||
*/
|
||||
@RepeatedTest(20)
|
||||
void testUnblockingByPlatformThread() throws Exception {
|
||||
testUnblocking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unblocking a virtual thread waiting to enter a monitor held by another
|
||||
* virtual thread.
|
||||
*/
|
||||
@RepeatedTest(20)
|
||||
void testUnblockingByVirtualThread() throws Exception {
|
||||
VThreadRunner.run(this::testUnblocking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test unblocking a virtual thread waiting to enter a monitor, monitor will be
|
||||
* initially be held by caller thread.
|
||||
*/
|
||||
private void testUnblocking() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
entered.set(true);
|
||||
}
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
started.await();
|
||||
|
||||
// random delay before exiting monitor
|
||||
switch (ThreadLocalRandom.current().nextInt(4)) {
|
||||
case 0 -> { /* no delay */}
|
||||
case 1 -> Thread.onSpinWait();
|
||||
case 2 -> Thread.yield();
|
||||
case 3 -> await(vthread, Thread.State.BLOCKED);
|
||||
default -> fail();
|
||||
}
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that unblocking a virtual thread waiting to enter a monitor does not consume
|
||||
* the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotConsumed() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
synchronized (lock) { } // should block
|
||||
LockSupport.park(); // should not park
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
vthread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that unblocking a virtual thread waiting to enter a monitor does not make
|
||||
* available the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotOffered() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) { } // should block
|
||||
LockSupport.park(); // should park
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
|
||||
try {
|
||||
// wait for thread to park, it should not terminate
|
||||
await(vthread, Thread.State.WAITING);
|
||||
vthread.join(Duration.ofMillis(100));
|
||||
assertEquals(Thread.State.WAITING, vthread.getState());
|
||||
} finally {
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
148
test/jdk/java/lang/Thread/virtual/MonitorEnterWaitOOME.java
Normal file
148
test/jdk/java/lang/Thread/virtual/MonitorEnterWaitOOME.java
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=monitorenter
|
||||
* @bug 8345266
|
||||
* @summary Test OOM while trying to unmount vthread on monitorenter
|
||||
* @requires vm.continuations & vm.gc.G1 & vm.opt.DisableExplicitGC != "true"
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseG1GC -Xmx48M MonitorEnterWaitOOME false
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timedwait
|
||||
* @summary Test OOM while trying to unmount vthread on Object.wait
|
||||
* @requires vm.continuations & vm.gc.G1 & vm.opt.DisableExplicitGC != "true"
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseG1GC -Xmx48M MonitorEnterWaitOOME true 5
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=untimedwait
|
||||
* @summary Test OOM while trying to unmount vthread on Object.wait
|
||||
* @requires vm.continuations & vm.gc.G1 & vm.opt.DisableExplicitGC != "true"
|
||||
* @library /test/lib
|
||||
* @run main/othervm -XX:+UseG1GC -Xmx48M MonitorEnterWaitOOME true 0
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class MonitorEnterWaitOOME {
|
||||
static volatile Object data;
|
||||
static Thread.State dummyState = Thread.State.RUNNABLE; // load java.lang.Thread$State
|
||||
|
||||
public static void main(String[] args) throws Throwable {
|
||||
final boolean testWait = args.length >= 1 ? Boolean.parseBoolean(args[0]) : false;
|
||||
final long timeout = testWait && args.length == 2 ? Long.parseLong(args[1]) : 0L;
|
||||
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
|
||||
Thread vthread;
|
||||
var lock = new Object();
|
||||
var canFillHeap = new AtomicBoolean();
|
||||
var heapFilled = new AtomicBoolean();
|
||||
var heapCollected = new AtomicBoolean();
|
||||
var exRef = new AtomicReference<Throwable>();
|
||||
synchronized (lock) {
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
awaitTrue(canFillHeap);
|
||||
data = fillHeap();
|
||||
heapFilled.set(true);
|
||||
synchronized (lock) {
|
||||
if (testWait) {
|
||||
lock.wait(timeout);
|
||||
}
|
||||
}
|
||||
data = null;
|
||||
System.gc();
|
||||
heapCollected.set(true);
|
||||
} catch (Throwable e) {
|
||||
data = null;
|
||||
System.gc(); // avoid nested OOME
|
||||
exRef.set(e);
|
||||
}
|
||||
});
|
||||
canFillHeap.set(true);
|
||||
awaitTrue(heapFilled);
|
||||
awaitState(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
if (testWait && timeout == 0) {
|
||||
awaitState(vthread, Thread.State.WAITING);
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
joinVThread(vthread, heapCollected, exRef);
|
||||
assert exRef.get() == null;
|
||||
}
|
||||
|
||||
private static Object[] fillHeap() {
|
||||
Object[] first = null, last = null;
|
||||
int size = 1 << 20;
|
||||
while (size > 0) {
|
||||
try {
|
||||
Object[] array = new Object[size];
|
||||
if (first == null) {
|
||||
first = array;
|
||||
} else {
|
||||
last[0] = array;
|
||||
}
|
||||
last = array;
|
||||
} catch (OutOfMemoryError oome) {
|
||||
size = size >>> 1;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
private static void awaitTrue(AtomicBoolean ready) {
|
||||
// Don't call anything that might allocate from the Java heap.
|
||||
while (!ready.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitState(Thread thread, Thread.State expectedState) {
|
||||
// Don't call anything that might allocate from the Java heap.
|
||||
while (thread.getState() != expectedState) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
private static void joinVThread(Thread vthread, AtomicBoolean ready, AtomicReference<Throwable> exRef) throws Throwable {
|
||||
// Don't call anything that might allocate from the Java heap until ready is set.
|
||||
while (!ready.get()) {
|
||||
Throwable ex = exRef.get();
|
||||
if (ex != null) {
|
||||
throw ex;
|
||||
}
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
476
test/jdk/java/lang/Thread/virtual/MonitorPinnedEvents.java
Normal file
476
test/jdk/java/lang/Thread/virtual/MonitorPinnedEvents.java
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test JFR jdk.VirtualThreadPinned event recorded for contended monitor enter
|
||||
* and Object.wait when pinned
|
||||
* @requires vm.continuations & vm.hasJFR
|
||||
* @modules jdk.jfr jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorPinnedEvents
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jfr.Recording;
|
||||
import jdk.jfr.consumer.RecordedEvent;
|
||||
import jdk.jfr.consumer.RecordedFrame;
|
||||
import jdk.jfr.consumer.RecordedMethod;
|
||||
import jdk.jfr.consumer.RecordedThread;
|
||||
import jdk.jfr.consumer.RecordingFile;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MonitorPinnedEvents {
|
||||
// log to System.err so inlined with JUnit output
|
||||
private static final PrintStream log = System.err;
|
||||
|
||||
// expected values for "blockingOperation" field in event
|
||||
private static final String CONTENDED_MONITOR_ENTER = "Contended monitor enter";
|
||||
private static final String OBJECT_WAIT = "Object.wait";
|
||||
|
||||
// expected value for "pinnedReason" field in event
|
||||
private static final String NATIVE_FRAME = "Native or VM frame on stack";
|
||||
|
||||
// block or wait for 2s to allow minimum event duration be tested
|
||||
private static final int DELAY = 2000;
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need at least two carriers to test pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for a contended monitor enter.
|
||||
*/
|
||||
@Test
|
||||
void testContentedMonitorEnter() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
|
||||
synchronized (lock) {
|
||||
// start virtual thread that blocks trying to acquire monitor while pinned
|
||||
var ready = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
synchronized (lock) { // <--- blocks here while pinned
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready, vthread, Thread.State.BLOCKED);
|
||||
|
||||
// sleep before releasing to ensure virtual thread is blocked for >= 2s
|
||||
Thread.sleep(DELAY);
|
||||
}
|
||||
vthread.join();
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
testEvent(event, vthread, CONTENDED_MONITOR_ENTER, NATIVE_FRAME, DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for a contended monitor enter where
|
||||
* another thread may acquire the monitor when the main thread releases it. If
|
||||
* the other thread acquires the monitor before the virtual thread then the event
|
||||
* duration should include the time blocked until the other thread releases it.
|
||||
*/
|
||||
@Test
|
||||
void testContentedMonitorEnter2() throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
// the thread that acquires the monitor after the main thread releases it
|
||||
var nextOwner = new AtomicReference<Thread>();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
Thread thread2;
|
||||
synchronized (lock) {
|
||||
|
||||
// start virtual thread that blocks trying to acquire monitor while pinned
|
||||
var ready1 = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready1.set(true);
|
||||
synchronized (lock) { // <--- blocks here while pinned
|
||||
nextOwner.compareAndSet(null, Thread.currentThread());
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready1, vthread, Thread.State.BLOCKED);
|
||||
|
||||
// start platform thread that blocks trying to acquire monitor
|
||||
thread2 = Thread.ofPlatform().start(() -> {
|
||||
synchronized (lock) {
|
||||
if (nextOwner.compareAndSet(null, Thread.currentThread())) {
|
||||
// delayed release of monitor if acquired before virtual thread
|
||||
try {
|
||||
Thread.sleep(DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} // release lock, vthread or thread2 will acquire
|
||||
|
||||
vthread.join();
|
||||
thread2.join();
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded. If the platform thread
|
||||
// acquired the monitor before the virtual thread then the event duration
|
||||
// should be >= DELAY.
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
Thread winner = nextOwner.get();
|
||||
assertNotNull(winner);
|
||||
int minDuration = winner.isVirtual() ? 0 : DELAY;
|
||||
testEvent(event, vthread, CONTENDED_MONITOR_ENTER, NATIVE_FRAME, minDuration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait when notified.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitNotify(boolean timed) throws Exception {
|
||||
testObjectWait(timed, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait when interrupted.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitInterrupt(boolean timed) throws Exception {
|
||||
testObjectWait(timed, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait.
|
||||
* @param timed true for a timed-wait, false for an untimed-wait
|
||||
* @param interrupt true to interrupt the thread, false to notify
|
||||
*/
|
||||
private void testObjectWait(boolean timed, boolean interrupt) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
|
||||
// start virtual thread that waits in Object.wait while pinned
|
||||
var ready = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
try {
|
||||
// wait while pinned
|
||||
if (timed) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready, vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// sleep to ensure virtual thread waits for >= 2s
|
||||
Thread.sleep(DELAY);
|
||||
|
||||
// interrupt or notify thread so it resumes execution
|
||||
if (interrupt) {
|
||||
vthread.interrupt();
|
||||
} else {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
vthread.join();
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
testEvent(event, vthread, OBJECT_WAIT, NATIVE_FRAME, DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait where the virtual thread
|
||||
* is notified but may block when attempting to reenter. One event should be
|
||||
* recorded. The event duration should include both the waiting time and the time
|
||||
* blocked to reenter.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitNotify2(boolean timed) throws Exception {
|
||||
testObjectWait2(timed, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait where the virtual thread
|
||||
* is interrupted but may block when attempting to reenter. One event should be
|
||||
* recorded. The event duration should include both the waiting time and the time
|
||||
* blocked to reenter.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testObjectWaitInterrupt2(boolean timed) throws Exception {
|
||||
testObjectWait2(timed, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test jdk.VirtualThreadPinned recorded for Object.wait where the virtual thread
|
||||
* blocks when attempting to reenter.
|
||||
* @param timed true for a timed-wait, false for an untimed-wait
|
||||
* @param interrupt true to interrupt the thread, false to notify
|
||||
*/
|
||||
private void testObjectWait2(boolean timed, boolean interrupt) throws Exception {
|
||||
try (Recording recording = new Recording()) {
|
||||
recording.enable("jdk.VirtualThreadPinned");
|
||||
recording.start();
|
||||
|
||||
// the thread that acquires the monitor after the main thread releases it
|
||||
var nextOwner = new AtomicReference<Thread>();
|
||||
|
||||
Thread vthread;
|
||||
try {
|
||||
Object lock = new Object();
|
||||
|
||||
// start virtual thread that waits in Object.wait while pinned
|
||||
var ready1 = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
boolean notify = !interrupt;
|
||||
synchronized (lock) {
|
||||
ready1.set(true);
|
||||
try {
|
||||
// wait while pinned
|
||||
if (timed) {
|
||||
lock.wait(Long.MAX_VALUE);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
if (notify) {
|
||||
nextOwner.compareAndSet(null, Thread.currentThread());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
if (interrupt) {
|
||||
nextOwner.compareAndSet(null, Thread.currentThread());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
await(ready1, vthread, timed ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// platform thread that blocks on monitor enter
|
||||
Thread thread2;
|
||||
synchronized (lock) {
|
||||
thread2 = Thread.ofPlatform().start(() -> {
|
||||
synchronized (lock) { // <--- blocks here
|
||||
if (nextOwner.compareAndSet(null, Thread.currentThread())) {
|
||||
// delayed release of monitor if acquired before virtual thread
|
||||
try {
|
||||
Thread.sleep(DELAY);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// interrupt/notify and release monitor to allow one of the threads to acquire
|
||||
if (interrupt) {
|
||||
vthread.interrupt();
|
||||
} else {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
vthread.join();
|
||||
thread2.join();
|
||||
|
||||
} finally {
|
||||
recording.stop();
|
||||
}
|
||||
|
||||
// jdk.VirtualThreadPinned event should be recorded. If the platform thread
|
||||
// acquired the monitor before the virtual thread re-entered then the event
|
||||
// duration should be >= DELAY.
|
||||
RecordedEvent event = findPinnedEvent(recording);
|
||||
Thread winner = nextOwner.get();
|
||||
assertNotNull(winner);
|
||||
int minDuration = winner.isVirtual() ? 0 : DELAY;
|
||||
testEvent(event, vthread, OBJECT_WAIT, NATIVE_FRAME, minDuration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the event was recorded by the expected virtual thread, the event has a
|
||||
* "carrierThread", the "blockingOperation", and "pinnedReason" fields have the
|
||||
* expected values, and the event duration is >= minDuration.
|
||||
*/
|
||||
private void testEvent(RecordedEvent event,
|
||||
Thread vthread,
|
||||
String expectedBlockingOp,
|
||||
String expectedPinnedReason,
|
||||
int minDuration) {
|
||||
assertTrue(vthread.isVirtual());
|
||||
assertEquals(vthread.threadId(), event.getThread().getId());
|
||||
|
||||
RecordedThread carrier = event.getValue("carrierThread");
|
||||
assertFalse(carrier.isVirtual());
|
||||
|
||||
assertEquals(expectedBlockingOp, event.getString("blockingOperation"));
|
||||
assertEquals(expectedPinnedReason, event.getString("pinnedReason"));
|
||||
|
||||
long duration = event.getDuration().toMillis();
|
||||
assertTrue(duration >= (minDuration - 100),
|
||||
"Duration " + duration + "ms, expected >= " + minDuration + "ms");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the expected jdk.VirtualThreadPinned event in the recording. There may be
|
||||
* several pinned events recorded by other parts of the system that need to be
|
||||
* filtered out.
|
||||
*/
|
||||
private RecordedEvent findPinnedEvent(Recording recording) throws IOException {
|
||||
Map<Boolean, List<RecordedEvent>> events = find(recording, "jdk.VirtualThreadPinned")
|
||||
.collect(Collectors.partitioningBy(e -> filtered(topFrameMethod(e)), Collectors.toList()));
|
||||
List<RecordedEvent> filteredEvents = events.get(Boolean.TRUE);
|
||||
List<RecordedEvent> pinnedEvents = events.get(Boolean.FALSE);
|
||||
log.format("%d event(s) recorded%n", filteredEvents.size() + pinnedEvents.size());
|
||||
log.println("-- filtered events --");
|
||||
log.println(filteredEvents);
|
||||
log.println("-- remaining pinned events --");
|
||||
log.println(pinnedEvents);
|
||||
assertEquals(1, pinnedEvents.size());
|
||||
return pinnedEvents.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pinned events recorded at the following classes/methods should be ignored.
|
||||
*/
|
||||
private final Set<String> FILTERED = Set.of(
|
||||
"java.lang.VirtualThread.*",
|
||||
"java.lang.ref.ReferenceQueue.poll",
|
||||
"java.lang.invoke.*",
|
||||
"jdk.internal.ref.*"
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the given top-frame {@code class.method} should be ignored.
|
||||
*/
|
||||
private boolean filtered(String topFrameMethod) {
|
||||
for (String s : FILTERED) {
|
||||
if (s.endsWith("*")) {
|
||||
String prefix = s.substring(0, s.length()-1);
|
||||
if (topFrameMethod.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
} else if (s.equals(topFrameMethod)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code class.method} of the top frame in the event's stack trace.
|
||||
*/
|
||||
private String topFrameMethod(RecordedEvent event) {
|
||||
RecordedFrame topFrame = event.getStackTrace().getFrames().get(0);
|
||||
RecordedMethod method = topFrame.getMethod();
|
||||
return method.getType().getName() + "." + method.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the events with the given name in the recording.
|
||||
*/
|
||||
private Stream<RecordedEvent> find(Recording recording, String name) throws IOException {
|
||||
Path recordingFile = recording.getDestination();
|
||||
if (recordingFile == null) {
|
||||
ProcessHandle h = ProcessHandle.current();
|
||||
recordingFile = Path.of("recording-" + recording.getId() + "-pid" + h.pid() + ".jfr");
|
||||
recording.dump(recordingFile);
|
||||
}
|
||||
return RecordingFile.readAllEvents(recordingFile)
|
||||
.stream()
|
||||
.filter(e -> e.getEventType().getName().equals(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for ready to become true, then waits for the thread to get to the given state.
|
||||
*/
|
||||
private void await(AtomicBoolean ready,
|
||||
Thread thread,
|
||||
Thread.State expectedState) throws InterruptedException {
|
||||
while (!ready.get()) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
803
test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
Normal file
803
test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads using Object.wait/notifyAll
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xint --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:-TieredCompilation --enable-native-access=ALL-UNNAMED MonitorWaitNotify
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class MonitorWaitNotify {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for testing pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread waits, notified by platform thread.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testWaitNotify1(boolean pinned) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
awaitTrue(ready);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test platform thread waits, notified by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testWaitNotify2() throws Exception {
|
||||
var lock = new Object();
|
||||
var thread = Thread.ofVirtual().unstarted(() -> {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
});
|
||||
synchronized (lock) {
|
||||
thread.start();
|
||||
lock.wait();
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test virtual thread waits, notified by another virtual thread.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testWaitNotify3(boolean pinned) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
});
|
||||
} else {
|
||||
ready.set(true);
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
var thread2 = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
awaitTrue(ready);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(thread1, Thread.State.BLOCKED);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notifyAll when there are no threads waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testNotifyBeforeWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
// no threads waiting
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
|
||||
// thread should wait
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
try {
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
Thread.State expectedState = timeout > 0
|
||||
? Thread.State.TIMED_WAITING
|
||||
: Thread.State.WAITING;
|
||||
await(thread, expectedState);
|
||||
|
||||
// poll thread state again, it should still be waiting
|
||||
Thread.sleep(10);
|
||||
assertEquals(thread.getState(), expectedState);
|
||||
} finally {
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of elements that are ordered pairs of platform and virtual thread
|
||||
* counts. 0,2,4,..8 platform threads. 2,4,6,..16 virtual threads.
|
||||
*/
|
||||
static Stream<Arguments> threadCounts() {
|
||||
return IntStream.range(0, 9)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(i -> i)
|
||||
.flatMap(np -> IntStream.range(2, 17)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(vp -> Arguments.of(np, vp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notify wakes only one thread when platform and virtual threads are waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testNotifyOneThread(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
int nThreads = nPlatformThreads + nVirtualThreads;
|
||||
|
||||
var lock = new Object();
|
||||
var ready = new CountDownLatch(nThreads);
|
||||
var notified = new AtomicInteger();
|
||||
|
||||
Runnable waitTask = () -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.countDown();
|
||||
lock.wait();
|
||||
notified.incrementAndGet();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
};
|
||||
|
||||
var threads = new ArrayList<Thread>();
|
||||
try {
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads.add(Thread.ofPlatform().start(waitTask));
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads.add(Thread.ofVirtual().start(waitTask));
|
||||
}
|
||||
|
||||
// wait for all threads to wait
|
||||
ready.await();
|
||||
|
||||
// wake threads, one by one
|
||||
for (int i = 0; i < threads.size(); i++) {
|
||||
|
||||
// wake one thread
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
|
||||
// one thread should have awoken
|
||||
int expectedWakeups = i + 1;
|
||||
while (notified.get() < expectedWakeups) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
assertEquals(expectedWakeups, notified.get());
|
||||
}
|
||||
} finally {
|
||||
for (Thread t : threads) {
|
||||
t.interrupt();
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test notifyAll wakes all threads.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testNotifyAllThreads(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
int nThreads = nPlatformThreads + nVirtualThreads;
|
||||
|
||||
var lock = new Object();
|
||||
var ready = new CountDownLatch(nThreads);
|
||||
var notified = new CountDownLatch(nThreads);
|
||||
|
||||
Runnable waitTask = () -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.countDown();
|
||||
lock.wait();
|
||||
notified.countDown();
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
};
|
||||
|
||||
var threads = new ArrayList<Thread>();
|
||||
try {
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads.add(Thread.ofPlatform().start(waitTask));
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads.add(Thread.ofVirtual().start(waitTask));
|
||||
}
|
||||
|
||||
// wait for all threads to wait
|
||||
ready.await();
|
||||
|
||||
// wakeup all threads
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// wait for all threads to have awoken
|
||||
notified.await();
|
||||
|
||||
} finally {
|
||||
for (Thread t : threads) {
|
||||
t.interrupt();
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duration of timed Object.wait.
|
||||
*/
|
||||
@Test
|
||||
void testTimedWaitDuration1() throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
var durationRef = new AtomicReference<Long>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
long start = millisTime();
|
||||
lock.wait(2000);
|
||||
durationRef.set(millisTime() - start);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
thread.join();
|
||||
|
||||
long duration = durationRef.get();
|
||||
checkDuration(duration, 1900, 20_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test duration of timed Object.wait. This test invokes wait twice, first with a short
|
||||
* timeout, the second with a longer timeout. The test scenario ensures that the
|
||||
* timeout from the first wait doesn't interfere with the second wait.
|
||||
*/
|
||||
@Test
|
||||
void testTimedWaitDuration2() throws Exception {
|
||||
var lock = new Object();
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var waited = new AtomicBoolean();
|
||||
var durationRef = new AtomicReference<Long>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
ready.set(true);
|
||||
lock.wait(200);
|
||||
waited.set(true);
|
||||
|
||||
long start = millisTime();
|
||||
lock.wait(2000);
|
||||
durationRef.set(millisTime() - start);
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
awaitTrue(ready);
|
||||
synchronized (lock) {
|
||||
// wake thread if waiting in first wait
|
||||
if (!waited.get()) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
thread.join();
|
||||
|
||||
long duration = durationRef.get();
|
||||
checkDuration(duration, 1900, 20_000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing invoking Object.wait with interrupted status set.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testWaitWithInterruptSet(int timeout) throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
Thread.currentThread().interrupt();
|
||||
if (timeout > 0) {
|
||||
assertThrows(InterruptedException.class, () -> lock.wait(timeout));
|
||||
} else {
|
||||
assertThrows(InterruptedException.class, lock::wait);
|
||||
}
|
||||
assertFalse(Thread.currentThread().isInterrupted());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interrupting a virtual thread waiting in Object.wait.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testInterruptWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var interruptedException = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// check stack trace has the expected frames
|
||||
Set<String> expected = Set.of("wait0", "wait", "run");
|
||||
Set<String> methods = Stream.of(e.getStackTrace())
|
||||
.map(StackTraceElement::getMethodName)
|
||||
.collect(Collectors.toSet());
|
||||
assertTrue(methods.containsAll(expected));
|
||||
|
||||
interruptedException.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// interrupt thread, should block, then throw InterruptedException
|
||||
synchronized (lock) {
|
||||
vthread.interrupt();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
vthread.join();
|
||||
assertTrue(interruptedException.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interrupting a virtual thread blocked waiting to reenter after waiting.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testInterruptReenterAfterWait(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var interruptedException = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
interruptedException.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
// interrupt when blocked
|
||||
vthread.interrupt();
|
||||
}
|
||||
|
||||
vthread.join();
|
||||
assertFalse(interruptedException.get());
|
||||
assertTrue(vthread.isInterrupted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Object.wait when the monitor entry count > 1.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testWaitWhenEnteredManyTimes(int timeout) throws Exception {
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
synchronized (lock) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// notify, thread should block waiting to reenter
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
}
|
||||
vthread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait does not consume the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotConsumed() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var completed = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
LockSupport.park(); // should not park
|
||||
completed.set(true);
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// wakeup thread
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// thread should terminate
|
||||
vthread.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait does not make available the thread's parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkingPermitNotOffered() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var readyToPark = new CountDownLatch(1);
|
||||
var completed = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
readyToPark.countDown();
|
||||
LockSupport.park(); // should park
|
||||
completed.set(true);
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
// wakeup thread
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// thread should park
|
||||
readyToPark.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
|
||||
LockSupport.unpark(vthread);
|
||||
|
||||
// thread should terminate
|
||||
vthread.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait releases the carrier. This test uses a custom scheduler
|
||||
* with one carrier thread.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testReleaseWhenWaiting1(int timeout) throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = new Object();
|
||||
var ready = new AtomicBoolean();
|
||||
var completed = new AtomicBoolean();
|
||||
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
ready.set(true);
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
fail("wait interrupted");
|
||||
}
|
||||
}
|
||||
completed.set(true);
|
||||
});
|
||||
vthread1.start();
|
||||
|
||||
// wait for vthread1 to start and wait
|
||||
awaitTrue(ready);
|
||||
await(vthread1, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
|
||||
// wakeup vthread1
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
vthread1.join();
|
||||
assertTrue(completed.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Object.wait releases the carrier. This test arranges for 4*ncores - 1
|
||||
* virtual threads to wait. For long timeout and no timeout cases, all virtual threads
|
||||
* will wait until they are notified.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 10, 20, 100, 500, 30000, Integer.MAX_VALUE })
|
||||
void testReleaseWhenWaiting2(int timeout) throws Exception {
|
||||
int VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
|
||||
CountDownLatch latch = new CountDownLatch(VTHREAD_COUNT);
|
||||
Object lock = new Object();
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
for (int i = 0; i < VTHREAD_COUNT; i++) {
|
||||
Thread.ofVirtual().name("vthread-" + i).start(() -> {
|
||||
synchronized (lock) {
|
||||
if (counter.incrementAndGet() == VTHREAD_COUNT) {
|
||||
lock.notifyAll();
|
||||
} else {
|
||||
try {
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
latch.await();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that wait(long) throws IAE when timeout is negative.
|
||||
*/
|
||||
@Test
|
||||
void testIllegalArgumentException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object obj = new Object();
|
||||
synchronized (obj) {
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L));
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L));
|
||||
assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that wait throws IMSE when not owner.
|
||||
*/
|
||||
@Test
|
||||
void testIllegalMonitorStateException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Object obj = new Object();
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait());
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0));
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000));
|
||||
assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current time in milliseconds.
|
||||
*/
|
||||
private static long millisTime() {
|
||||
long now = System.nanoTime();
|
||||
return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a duration is within expected bounds.
|
||||
* @param duration, in milliseconds
|
||||
* @param min minimum expected duration, in milliseconds
|
||||
* @param max maximum expected duration, in milliseconds
|
||||
* @return the duration (now - start), in milliseconds
|
||||
*/
|
||||
private static void checkDuration(long duration, long min, long max) {
|
||||
assertTrue(duration >= min,
|
||||
"Duration " + duration + "ms, expected >= " + min + "ms");
|
||||
assertTrue(duration <= max,
|
||||
"Duration " + duration + "ms, expected <= " + max + "ms");
|
||||
}
|
||||
}
|
||||
102
test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java
Normal file
102
test/jdk/java/lang/Thread/virtual/ParkWithFixedThreadPool.java
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (C) 2021, Tencent. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test virtual thread park when scheduler is a fixed thread pool
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main ParkWithFixedThreadPool
|
||||
*/
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
public class ParkWithFixedThreadPool {
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (var scheduler = new Scheduler(8)) {
|
||||
int vthreadCount = 300;
|
||||
Thread[] vthreads = new Thread[vthreadCount];
|
||||
Runnable target = new Runnable() {
|
||||
public void run() {
|
||||
int myIndex = -1;
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
if (vthreads[i] == Thread.currentThread()) {
|
||||
myIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (myIndex > 0) {
|
||||
LockSupport.unpark(vthreads[myIndex - 1]);
|
||||
}
|
||||
|
||||
if (myIndex != (vthreadCount - 1)) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i] = factory.newThread(target);
|
||||
}
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i].start();
|
||||
}
|
||||
|
||||
for (int i = 0; i < vthreadCount; i++) {
|
||||
vthreads[i].join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Scheduler implements Executor, AutoCloseable {
|
||||
private final ExecutorService pool;
|
||||
|
||||
Scheduler(int poolSize) {
|
||||
pool = Executors.newFixedThreadPool(poolSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
try {
|
||||
pool.execute(task);
|
||||
} finally {
|
||||
// ExecutorService::execute may consume parking permit
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
487
test/jdk/java/lang/Thread/virtual/Parking.java
Normal file
487
test/jdk/java/lang/Thread/virtual/Parking.java
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads using park/unpark
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/timeout=480 Parking
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/timeout=480 -Xint Parking
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/timeout=480 -Xcomp Parking
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation Parking
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class Parking {
|
||||
static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
|
||||
static final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* Park, unparked by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark1() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(LockSupport::park);
|
||||
Thread.sleep(1000); // give time for virtual thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park, unparked by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark2() throws Exception {
|
||||
var thread1 = Thread.ofVirtual().start(LockSupport::park);
|
||||
Thread.sleep(1000); // give time for virtual thread to park
|
||||
var thread2 = Thread.ofVirtual().start(() -> LockSupport.unpark(thread1));
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park while holding monitor, unparked by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark3() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
Thread.sleep(1000); // give time for virtual thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with native frame on stack.
|
||||
*/
|
||||
@Test
|
||||
void testPark4() throws Exception {
|
||||
// not implemented
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpark before park.
|
||||
*/
|
||||
@Test
|
||||
void testPark5() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
LockSupport.park();
|
||||
});
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* 2 x unpark before park.
|
||||
*/
|
||||
@Test
|
||||
void testPark6() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
Thread me = Thread.currentThread();
|
||||
LockSupport.unpark(me);
|
||||
LockSupport.unpark(me);
|
||||
LockSupport.park();
|
||||
LockSupport.park(); // should park
|
||||
});
|
||||
Thread.sleep(1000); // give time for thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* 2 x park and unpark by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testPark7() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.park();
|
||||
LockSupport.park();
|
||||
});
|
||||
|
||||
Thread.sleep(1000); // give time for thread to park
|
||||
|
||||
// unpark, virtual thread should park again
|
||||
LockSupport.unpark(thread);
|
||||
Thread.sleep(1000);
|
||||
assertTrue(thread.isAlive());
|
||||
|
||||
// let it terminate
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testPark8() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
LockSupport.park();
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked.
|
||||
*/
|
||||
@Test
|
||||
void testPark9() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Park while holding monitor and with interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testPark10() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked while holding monitor
|
||||
*/
|
||||
@Test
|
||||
void testPark11() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* parkNanos(-1) completes immediately
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos1() throws Exception {
|
||||
VThreadRunner.run(() -> LockSupport.parkNanos(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* parkNanos(0) completes immediately
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos2() throws Exception {
|
||||
VThreadRunner.run(() -> LockSupport.parkNanos(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* parkNanos(1000ms) parks thread.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
// park for 1000ms
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1000, TimeUnit.MILLISECONDS);
|
||||
long start = System.nanoTime();
|
||||
LockSupport.parkNanos(nanos);
|
||||
|
||||
// check that virtual thread parked for >= 900ms
|
||||
long elapsed = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start,
|
||||
TimeUnit.NANOSECONDS);
|
||||
assertTrue(elapsed >= 900);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos, unparked by platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos4() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
|
||||
LockSupport.parkNanos(nanos);
|
||||
});
|
||||
Thread.sleep(100); // give time for virtual thread to park
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos, unparked by virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos5() throws Exception {
|
||||
var thread1 = Thread.ofVirtual().start(() -> {
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
|
||||
LockSupport.parkNanos(nanos);
|
||||
});
|
||||
Thread.sleep(100); // give time for virtual thread to park
|
||||
var thread2 = Thread.ofVirtual().start(() -> LockSupport.unpark(thread1));
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpark before parkNanos.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos6() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
|
||||
LockSupport.parkNanos(nanos);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpark before parkNanos(0), should consume parking permit.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos7() throws Exception {
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
LockSupport.unpark(Thread.currentThread());
|
||||
LockSupport.parkNanos(0); // should consume parking permit
|
||||
LockSupport.park(); // should block
|
||||
});
|
||||
boolean isAlive = thread.join(Duration.ofSeconds(2));
|
||||
assertTrue(isAlive);
|
||||
LockSupport.unpark(thread);
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos and interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos8() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked in parkNanos.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos9() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Park with parkNanos while holding monitor and with interrupted status set.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos10() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
t.interrupt();
|
||||
synchronized (lock) {
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
}
|
||||
assertTrue(t.isInterrupted());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread interrupt when parked in parkNanos and while holding monitor.
|
||||
*/
|
||||
@Test
|
||||
void testParkNanos11() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Thread t = Thread.currentThread();
|
||||
scheduleInterrupt(t, 1000);
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
synchronized (lock) {
|
||||
LockSupport.parkNanos(Duration.ofDays(1).toNanos());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that parking while holding a monitor releases the carrier.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
void testParkWhenHoldingMonitor(boolean reenter) throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = new Object();
|
||||
|
||||
// thread enters (and maybe reenters) a monitor and parks
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
if (reenter) {
|
||||
synchronized (lock) {
|
||||
LockSupport.park();
|
||||
}
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
vthread1.start();
|
||||
try {
|
||||
// wait for thread to start and park
|
||||
started.await();
|
||||
await(vthread1, Thread.State.WAITING);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
} finally {
|
||||
LockSupport.unpark(vthread1);
|
||||
vthread1.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lots of virtual threads parked while holding a monitor. If the number of
|
||||
* virtual threads exceeds the number of carrier threads then this test will hang if
|
||||
* parking doesn't release the carrier.
|
||||
*/
|
||||
@Test
|
||||
void testManyParkedWhenHoldingMonitor() throws Exception {
|
||||
Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
|
||||
var done = new AtomicBoolean();
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
});
|
||||
// wait for thread to start and park
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
vthreads[i] = vthread;
|
||||
}
|
||||
|
||||
// cleanup
|
||||
done.set(true);
|
||||
for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
|
||||
var vthread = vthreads[i];
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a thread to be interrupted after a delay.
|
||||
*/
|
||||
private static void scheduleInterrupt(Thread thread, long delay) {
|
||||
Runnable interruptTask = () -> {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
thread.interrupt();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
};
|
||||
new Thread(interruptTask).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
393
test/jdk/java/lang/Thread/virtual/Reflection.java
Normal file
393
test/jdk/java/lang/Thread/virtual/Reflection.java
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test virtual threads using core reflection
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit Reflection
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assumptions.*;
|
||||
|
||||
class Reflection {
|
||||
|
||||
/**
|
||||
* Test invoking static method.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
int result = (int) divideMethod().invoke(null, 20, 2);
|
||||
assertTrue(result == 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that InvocationTargetException is thrown when a static method throws
|
||||
* exception.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
try {
|
||||
divideMethod().invoke(null, 20, 0);
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(e.getCause() instanceof ArithmeticException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that IllegalArgumentException is thrown when trying to invoke a static
|
||||
* method with bad parameters.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(null, 1));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(null, 1, 2, 3));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(new Object()));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(new Object(), 1));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> divideMethod().invoke(new Object(), 1, 2, 3));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ExceptionInInitializerError is thrown when invoking a static
|
||||
* method triggers its class to be initialized and it fails with exception.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic4() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Method foo = BadClass1.class.getDeclaredMethod("foo");
|
||||
try {
|
||||
foo.invoke(null);
|
||||
fail();
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
assertTrue(e.getCause() instanceof ArithmeticException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass1 {
|
||||
static {
|
||||
if (1==1) throw new ArithmeticException();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an error is thrown when invoking a static method triggers its
|
||||
* class to be initialized and it fails with an error.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic5() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Method foo = BadClass2.class.getDeclaredMethod("foo");
|
||||
assertThrows(AbstractMethodError.class, () -> foo.invoke(null));
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass2 {
|
||||
static {
|
||||
if (1==1) throw new AbstractMethodError();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that invoking a static method does not pin the carrier thread.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeStatic6() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Method parkMethod = Parker.class.getDeclaredMethod("park");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new CountDownLatch(1);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
ready.countDown();
|
||||
try {
|
||||
parkMethod.invoke(null); // blocks
|
||||
} catch (Exception e) { }
|
||||
});
|
||||
vthread.start();
|
||||
|
||||
try {
|
||||
// wait for thread to run
|
||||
ready.await();
|
||||
|
||||
// unpark with another virtual thread, runs on same carrier thread
|
||||
Thread unparker = factory.newThread(() -> LockSupport.unpark(vthread));
|
||||
unparker.start();
|
||||
unparker.join();
|
||||
} finally {
|
||||
LockSupport.unpark(vthread); // in case test fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invoking instance method.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeInstance1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
Adder.addMethod().invoke(adder, 5);
|
||||
assertTrue(adder.sum == 5);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that InvocationTargetException is thrown when an instance method throws
|
||||
* exception.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeInstance2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
try {
|
||||
Adder.addMethod().invoke(adder, -5);
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that NullPointerException and IllegalArgumentException are thrown when
|
||||
* trying to invoke an instance method with null or bad parameters.
|
||||
*/
|
||||
@Test
|
||||
void testInvokeInstance3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
Method addMethod = Adder.addMethod();
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> addMethod.invoke(null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder, 1, 2));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder, 1, "hi"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> addMethod.invoke(adder, "hi"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test invoking newInstance to create an object.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance1() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = Adder.class.getDeclaredConstructor(long.class);
|
||||
Adder adder = (Adder) ctor.newInstance(10);
|
||||
assertTrue(adder.sum == 10);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that InvocationTargetException is thrown when a constructor throws
|
||||
* exception.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = Adder.class.getDeclaredConstructor(long.class);
|
||||
try {
|
||||
ctor.newInstance(-10);
|
||||
fail();
|
||||
} catch (InvocationTargetException e) {
|
||||
assertTrue(e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that IllegalArgumentException is thrown when newInstacne is called
|
||||
* with bad parameters.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance3() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
var adder = new Adder();
|
||||
Constructor<?> ctor = Adder.class.getDeclaredConstructor(long.class);
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance((Object[])null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder, null));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder, "foo"));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> ctor.newInstance(adder, 1, 2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that ExceptionInInitializerError is thrown when invoking newInstance
|
||||
* triggers the class to be initialized and it fails with exception.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance4() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = BadClass3.class.getDeclaredConstructor();
|
||||
try {
|
||||
ctor.newInstance((Object[])null);
|
||||
fail();
|
||||
} catch (ExceptionInInitializerError e) {
|
||||
assertTrue(e.getCause() instanceof ArithmeticException);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass3 {
|
||||
static {
|
||||
if (1==1) throw new ArithmeticException();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that error is thrown when invoking newInstance triggers the class
|
||||
* to be initialized and it fails with an error.
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance5() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Constructor<?> ctor = BadClass4.class.getDeclaredConstructor();
|
||||
assertThrows(AbstractMethodError.class, () -> ctor.newInstance((Object[])null));
|
||||
});
|
||||
}
|
||||
|
||||
static class BadClass4 {
|
||||
static {
|
||||
if (1==1) throw new AbstractMethodError();
|
||||
}
|
||||
static void foo() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that newInstance does not pin the carrier thread
|
||||
*/
|
||||
@Test
|
||||
void testNewInstance6() throws Exception {
|
||||
assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
Constructor<?> ctor = Parker.class.getDeclaredConstructor();
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new CountDownLatch(1);
|
||||
Thread vthread = factory.newThread(() -> {
|
||||
ready.countDown();
|
||||
try {
|
||||
ctor.newInstance();
|
||||
} catch (Exception e) { }
|
||||
});
|
||||
vthread.start();
|
||||
|
||||
try {
|
||||
// wait for thread to run
|
||||
ready.await();
|
||||
|
||||
// unpark with another virtual thread, runs on same carrier thread
|
||||
Thread unparker = factory.newThread(() -> LockSupport.unpark(vthread));
|
||||
unparker.start();
|
||||
unparker.join();
|
||||
} finally {
|
||||
LockSupport.unpark(vthread); // in case test fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -- support classes and methods --
|
||||
|
||||
static int divide(int x, int y) {
|
||||
return x / y;
|
||||
}
|
||||
|
||||
static Method divideMethod() throws NoSuchMethodException {
|
||||
return Reflection.class.getDeclaredMethod("divide", int.class, int.class);
|
||||
}
|
||||
|
||||
static class Adder {
|
||||
long sum;
|
||||
Adder() { }
|
||||
Adder(long x) {
|
||||
if (x < 0)
|
||||
throw new IllegalArgumentException();
|
||||
sum = x;
|
||||
}
|
||||
Adder add(long x) {
|
||||
if (x < 0)
|
||||
throw new IllegalArgumentException();
|
||||
sum += x;
|
||||
return this;
|
||||
}
|
||||
static Method addMethod() throws NoSuchMethodException {
|
||||
return Adder.class.getDeclaredMethod("add", long.class);
|
||||
}
|
||||
long sum() {
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
static class Parker {
|
||||
Parker() {
|
||||
LockSupport.park();
|
||||
}
|
||||
static void park() {
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test that a virtual thread waiting to enter/reenter a monitor, while pinning
|
||||
* its carrier, will retry until it enters the monitor. This avoids starvation when the
|
||||
* monitor is exited, an unmounted thread is the chosen successor, and the successor
|
||||
* can't continue because there are no carriers available.
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED RetryMonitorEnterWhenPinned
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.RepeatedTest;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
public class RetryMonitorEnterWhenPinned {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() {
|
||||
// need >=2 carriers for testing pinning when main thread is a virtual thread
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
@RepeatedTest(10)
|
||||
void testMonitorEnter() throws Exception {
|
||||
var threads = new ArrayList<Thread>();
|
||||
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
|
||||
// start virtual threads that block on monitorenter
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
started.countDown();
|
||||
synchronized (lock) {
|
||||
spin(20);
|
||||
}
|
||||
});
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// start virtual threads that block on monitorenter while pinned
|
||||
int carriersAvailable = Runtime.getRuntime().availableProcessors();
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
carriersAvailable--;
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
started.countDown();
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
spin(20);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// if there are carriers available when wait until the thread blocks.
|
||||
if (carriersAvailable > 0) {
|
||||
System.out.printf("%s waiting for thread #%d to block%n",
|
||||
Instant.now(), thread.threadId());
|
||||
started.await();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
carriersAvailable--;
|
||||
}
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
} // exit monitor
|
||||
|
||||
// wait for all threads to terminate
|
||||
int threadsRemaining = threads.size();
|
||||
while (threadsRemaining > 0) {
|
||||
System.out.printf("%s waiting for %d threads to terminate%n",
|
||||
Instant.now(), threadsRemaining);
|
||||
int terminated = 0;
|
||||
for (Thread t : threads) {
|
||||
if (t.join(Duration.ofSeconds(1))) {
|
||||
terminated++;
|
||||
}
|
||||
}
|
||||
threadsRemaining = threads.size() - terminated;
|
||||
}
|
||||
System.out.printf("%s done%n", Instant.now());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = { 0, 30000, Integer.MAX_VALUE })
|
||||
void testMonitorReenter(int timeout) throws Exception {
|
||||
var threads = new ArrayList<Thread>();
|
||||
|
||||
Object lock = new Object();
|
||||
|
||||
// start virtual threads that wait on Object.wait
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
started.countDown();
|
||||
if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
|
||||
// wait for thread to start and wait
|
||||
started.await();
|
||||
await(thread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// start virtual threads that wait on Object.wait while pinnned
|
||||
int carriersAvailable = Runtime.getRuntime().availableProcessors();
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
carriersAvailable--;
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
boolean hasCarrier = carriersAvailable > 0;
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
try {
|
||||
synchronized (lock) {
|
||||
started.countDown();
|
||||
if (!hasCarrier) {
|
||||
// This thread will run at the very
|
||||
// end and won't be notified.
|
||||
lock.wait(1);
|
||||
} else if (timeout > 0) {
|
||||
lock.wait(timeout);
|
||||
} else {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) { }
|
||||
});
|
||||
});
|
||||
|
||||
// if there are carriers available when wait until the thread blocks.
|
||||
if (hasCarrier) {
|
||||
System.out.printf("%s waiting for thread #%d to block%n",
|
||||
Instant.now(), thread.threadId());
|
||||
started.await();
|
||||
await(thread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING);
|
||||
carriersAvailable--;
|
||||
}
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// wakeup all threads
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
|
||||
// wait for all threads to terminate
|
||||
int threadsRemaining = threads.size();
|
||||
while (threadsRemaining > 0) {
|
||||
System.out.printf("%s waiting for %d threads to terminate%n",
|
||||
Instant.now(), threadsRemaining);
|
||||
int terminated = 0;
|
||||
for (Thread t : threads) {
|
||||
if (t.join(Duration.ofSeconds(1))) {
|
||||
terminated++;
|
||||
}
|
||||
}
|
||||
threadsRemaining = threads.size() - terminated;
|
||||
}
|
||||
System.out.printf("%s done%n", Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Spin for the given number of milliseconds.
|
||||
*/
|
||||
static void spin(long millis) {
|
||||
long nanos = TimeUnit.MILLISECONDS.toNanos(millis);
|
||||
long start = System.nanoTime();
|
||||
while ((System.nanoTime() - start) < nanos) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a thread to reach the expected state.
|
||||
*/
|
||||
static void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assert state != Thread.State.TERMINATED : "Thread has terminated";
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
test/jdk/java/lang/Thread/virtual/ShutdownHook.java
Normal file
53
test/jdk/java/lang/Thread/virtual/ShutdownHook.java
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @summary Shutdown hook is called with virtual thread
|
||||
* @run main ShutdownHook test
|
||||
*/
|
||||
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import static jdk.test.lib.process.ProcessTools.createTestJavaProcessBuilder;
|
||||
import static jdk.test.lib.process.ProcessTools.executeProcess;
|
||||
|
||||
public class ShutdownHook {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0) {
|
||||
ProcessBuilder pb = createTestJavaProcessBuilder("ShutdownHook");
|
||||
OutputAnalyzer output = executeProcess(pb);
|
||||
output.shouldContain("Shutdown Hook");
|
||||
output.shouldHaveExitValue(0);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread thread = Thread.ofVirtual().unstarted(()-> {
|
||||
System.out.println("Shutdown Hook");
|
||||
System.out.flush();
|
||||
});
|
||||
Runtime.getRuntime().addShutdownHook(thread);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
160
test/jdk/java/lang/Thread/virtual/StackFrames.java
Normal file
160
test/jdk/java/lang/Thread/virtual/StackFrames.java
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test stack traces in exceptions, stack frames walked by the StackWalker,
|
||||
* and the stack trace returned by Thread.getStackTrace
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open java.management
|
||||
* @library /test/lib
|
||||
* @run junit StackFrames
|
||||
* @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+ShowCarrierFrames StackFrames
|
||||
*/
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
import static java.lang.StackWalker.Option.*;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class StackFrames {
|
||||
|
||||
/**
|
||||
* Test that the stack trace in exceptions does not include the carrier thread
|
||||
* frames, except when running with -XX:+ShowCarrierFrames.
|
||||
*/
|
||||
@Test
|
||||
void testStackTraceException() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
Exception e = new Exception();
|
||||
boolean found = Arrays.stream(e.getStackTrace())
|
||||
.map(StackTraceElement::getClassName)
|
||||
.anyMatch("java.util.concurrent.ForkJoinPool"::equals);
|
||||
assertTrue(found == hasJvmArgument("-XX:+ShowCarrierFrames"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that StackWalker does not include carrier thread frames in the stream of
|
||||
* stack frames.
|
||||
*/
|
||||
@Test
|
||||
void testStackWalker() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
StackWalker walker = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE));
|
||||
boolean found = walker.walk(sf ->
|
||||
sf.map(StackWalker.StackFrame::getDeclaringClass)
|
||||
.anyMatch(c -> c == ForkJoinPool.class));
|
||||
assertFalse(found);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.getStackTrace returns the expected bottom frame for both the carrier
|
||||
* and virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testBottomFrames() throws Exception {
|
||||
try (ForkJoinPool pool = new ForkJoinPool(1)) {
|
||||
var carrierRef = new AtomicReference<Thread>();
|
||||
Executor scheduler = task -> {
|
||||
pool.submit(() -> {
|
||||
carrierRef.set(Thread.currentThread());
|
||||
task.run();
|
||||
});
|
||||
};
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var ready = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
|
||||
// create virtual thread to use custom scheduler
|
||||
var vthread = factory.newThread(() -> {
|
||||
ready.set(true);
|
||||
while (!done.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
});
|
||||
|
||||
vthread.start();
|
||||
try {
|
||||
awaitTrue(ready);
|
||||
|
||||
// get carrier Thread
|
||||
Thread carrier = carrierRef.get();
|
||||
assertTrue(carrier instanceof ForkJoinWorkerThread);
|
||||
|
||||
// bottom-most frame of virtual thread should be VirtualThread.run
|
||||
System.err.println(vthread);
|
||||
StackTraceElement[] vthreadStack = vthread.getStackTrace();
|
||||
Stream.of(vthreadStack).forEach(e -> System.err.println(" " + e));
|
||||
StackTraceElement bottomFrame = vthreadStack[vthreadStack.length - 1];
|
||||
assertEquals("java.lang.VirtualThread.run",
|
||||
bottomFrame.getClassName() + "." + bottomFrame.getMethodName());
|
||||
|
||||
// bottom-most frame of carrier thread should be Thread.run
|
||||
System.err.println(carrier);
|
||||
StackTraceElement[] carrierStack = carrier.getStackTrace();
|
||||
Stream.of(carrierStack).forEach(e -> System.err.println(" " + e));
|
||||
bottomFrame = carrierStack[carrierStack.length - 1];
|
||||
assertEquals("java.util.concurrent.ForkJoinWorkerThread.run",
|
||||
bottomFrame.getClassName() + "." + bottomFrame.getMethodName());
|
||||
|
||||
} finally {
|
||||
done.set(true);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if started with the given VM option.
|
||||
*/
|
||||
private static boolean hasJvmArgument(String arg) {
|
||||
for (String argument : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
|
||||
if (argument.equals(arg)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the boolean value to become true.
|
||||
*/
|
||||
private static void awaitTrue(AtomicBoolean ref) throws InterruptedException {
|
||||
while (!ref.get()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
test/jdk/java/lang/Thread/virtual/Starvation.java
Normal file
92
test/jdk/java/lang/Thread/virtual/Starvation.java
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @requires vm.continuations
|
||||
* @library /test/lib
|
||||
* @bug 8345294
|
||||
* @run main/othervm/native --enable-native-access=ALL-UNNAMED Starvation
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class Starvation {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
int nprocs = Runtime.getRuntime().availableProcessors();
|
||||
iterations = 40_000 / nprocs;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
System.out.format("%s iteration %d of %d ...%n", Instant.now(), i, iterations);
|
||||
var exRef = new AtomicReference<Exception>();
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
runTest();
|
||||
} catch (Exception e) {
|
||||
exRef.set(e);
|
||||
}
|
||||
});
|
||||
while (!thread.join(Duration.ofSeconds(1))) {
|
||||
System.out.format("%s iteration %d waiting for %s%n", Instant.now(), i, thread);
|
||||
}
|
||||
Exception ex = exRef.get();
|
||||
if (ex != null) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void runTest() throws InterruptedException {
|
||||
int nprocs = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
var threads = new ArrayList<Thread>();
|
||||
Object lock = new Object();
|
||||
synchronized (lock) {
|
||||
for (int i = 0; i < nprocs - 1; i++) {
|
||||
var started = new CountDownLatch(1);
|
||||
Thread thread = Thread.startVirtualThread(() -> {
|
||||
started.countDown();
|
||||
VThreadPinner.runPinned(() -> {
|
||||
synchronized (lock) {
|
||||
}
|
||||
});
|
||||
});
|
||||
started.await();
|
||||
threads.add(thread);
|
||||
}
|
||||
}
|
||||
|
||||
for (Thread t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
511
test/jdk/java/lang/Thread/virtual/SynchronizedNative.java
Normal file
511
test/jdk/java/lang/Thread/virtual/SynchronizedNative.java
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads with a synchronized native method and a native method
|
||||
* that enter/exits a monitor with JNI MonitorEnter/MonitorExit
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xint
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xint --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-TieredStopAtLevel1
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:TieredStopAtLevel=1 --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=Xcomp-noTieredCompilation
|
||||
* @requires vm.continuations
|
||||
* @modules java.base/java.lang:+open jdk.management
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 -Xcomp -XX:-TieredCompilation --enable-native-access=ALL-UNNAMED SynchronizedNative
|
||||
*/
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Phaser;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SynchronizedNative {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws Exception {
|
||||
// need at least two carriers to test pinning
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
System.loadLibrary("SynchronizedNative");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with a synchronized native method, no contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnter() throws Exception {
|
||||
Object lock = this;
|
||||
VThreadRunner.run(() -> {
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reentering a monitor with synchronized native method, no contention.
|
||||
*/
|
||||
@Test
|
||||
void testReenter() throws Exception {
|
||||
Object lock = this;
|
||||
VThreadRunner.run(() -> {
|
||||
|
||||
// enter, reenter with a synchronized native method
|
||||
synchronized (lock) {
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with synchronized native method, renter with synchronized statement
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with synchronized native method, reenter with synchronized native method
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with a synchronized native method and with contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnterWithContention() throws Exception {
|
||||
var lock = this;
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
});
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of elements that are ordered pairs of platform and virtual thread
|
||||
* counts. 0,2,4 platform threads. 2,4,6,8 virtual threads.
|
||||
*/
|
||||
static Stream<Arguments> threadCounts() {
|
||||
return IntStream.range(0, 5)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(i -> i)
|
||||
.flatMap(np -> IntStream.range(2, 9)
|
||||
.filter(i -> i % 2 == 0)
|
||||
.mapToObj(vp -> Arguments.of(np, vp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task concurrently from both platform and virtual threads.
|
||||
*/
|
||||
private void executeConcurrently(int nPlatformThreads,
|
||||
int nVirtualThreads,
|
||||
Runnable task) throws Exception {
|
||||
int parallism = nVirtualThreads;
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
parallism++;
|
||||
}
|
||||
int previousParallelism = VThreadRunner.ensureParallelism(parallism);
|
||||
try {
|
||||
int nthreads = nPlatformThreads + nVirtualThreads;
|
||||
var phaser = new Phaser(nthreads + 1);
|
||||
|
||||
// start all threads
|
||||
var threads = new Thread[nthreads];
|
||||
int index = 0;
|
||||
for (int i = 0; i < nPlatformThreads; i++) {
|
||||
threads[index++] = Thread.ofPlatform().start(() -> {
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
task.run();
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < nVirtualThreads; i++) {
|
||||
threads[index++] = Thread.ofVirtual().start(() -> {
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
task.run();
|
||||
});
|
||||
}
|
||||
|
||||
// wait for all threads to start
|
||||
phaser.arriveAndAwaitAdvance();
|
||||
System.err.printf(" %d threads started%n", nthreads);
|
||||
|
||||
// wait for all threads to terminate
|
||||
for (Thread thread : threads) {
|
||||
if (thread != null) {
|
||||
System.err.printf(" join %s ...%n", thread);
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// reset parallelism
|
||||
VThreadRunner.setParallelism(previousParallelism);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test entering a monitor with a synchronized native method from many threads
|
||||
* at the same time.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testEnterConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
var counter = new Object() {
|
||||
int value;
|
||||
int value() { return value; }
|
||||
void increment() { value++; }
|
||||
};
|
||||
var lock = this;
|
||||
executeConcurrently(nPlatformThreads, nVirtualThreads, () -> {
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
counter.increment();
|
||||
LockSupport.parkNanos(100_000_000); // 100ms
|
||||
});
|
||||
});
|
||||
synchronized (lock) {
|
||||
assertEquals(nPlatformThreads + nVirtualThreads, counter.value());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with JNI MonitorEnter.
|
||||
*/
|
||||
@Test
|
||||
void testEnterInNative() throws Exception {
|
||||
Object lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reentering a monitor with JNI MonitorEnter.
|
||||
*/
|
||||
@Test
|
||||
void testReenterInNative() throws Exception {
|
||||
Object lock = new Object();
|
||||
VThreadRunner.run(() -> {
|
||||
|
||||
// enter, reenter with JNI MonitorEnter
|
||||
synchronized (lock) {
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with JNI MonitorEnter, renter with synchronized statement
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
synchronized (lock) {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
}
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
|
||||
// enter with JNI MonitorEnter, renter with JNI MonitorEnter
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with JNI MonitorEnter and with contention.
|
||||
*/
|
||||
@Test
|
||||
void testEnterInNativeWithContention() throws Exception {
|
||||
var lock = new Object();
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
});
|
||||
});
|
||||
try {
|
||||
synchronized (lock) {
|
||||
vthread.start();
|
||||
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertFalse(entered.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test entering a monitor with JNI MonitorEnter from many threads at the same time.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("threadCounts")
|
||||
void testEnterInNativeConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception {
|
||||
var counter = new Object() {
|
||||
int value;
|
||||
int value() { return value; }
|
||||
void increment() { value++; }
|
||||
};
|
||||
var lock = counter;
|
||||
executeConcurrently(nPlatformThreads, nVirtualThreads, () -> {
|
||||
runWithMonitorEnteredInNative(lock, () -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
counter.increment();
|
||||
LockSupport.parkNanos(100_000_000); // 100ms
|
||||
});
|
||||
});
|
||||
synchronized (lock) {
|
||||
assertEquals(nPlatformThreads + nVirtualThreads, counter.value());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test parking with synchronized native method on stack.
|
||||
*/
|
||||
@Test
|
||||
void testParkingWhenPinned() throws Exception {
|
||||
var lock = this;
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean();
|
||||
var done = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
while (!done.get()) {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
// wait for thread to start and block
|
||||
started.await();
|
||||
await(vthread, Thread.State.WAITING);
|
||||
} finally {
|
||||
done.set(true);
|
||||
LockSupport.unpark(vthread);
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test blocking with synchronized native method on stack.
|
||||
*/
|
||||
@Test
|
||||
void testBlockingWhenPinned() throws Exception {
|
||||
var lock1 = this;
|
||||
var lock2 = new Object();
|
||||
|
||||
var started = new CountDownLatch(1);
|
||||
var entered1 = new AtomicBoolean(); // set to true when vthread enters lock1
|
||||
var entered2 = new AtomicBoolean(); // set to true when vthread enters lock2
|
||||
|
||||
var vthread = Thread.ofVirtual().unstarted(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock1));
|
||||
entered1.set(true);
|
||||
synchronized (lock2) { // should block
|
||||
assertTrue(Thread.holdsLock(lock2));
|
||||
entered2.set(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
try {
|
||||
synchronized (lock2) {
|
||||
// start thread and wait for it to block trying to enter lock2
|
||||
vthread.start();
|
||||
started.await();
|
||||
await(vthread, Thread.State.BLOCKED);
|
||||
|
||||
assertTrue(entered1.get());
|
||||
assertFalse(entered2.get());
|
||||
}
|
||||
} finally {
|
||||
vthread.join();
|
||||
}
|
||||
assertTrue(entered2.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that blocking on synchronized native method releases the carrier.
|
||||
*/
|
||||
//@Test
|
||||
void testReleaseWhenBlocked() throws Exception {
|
||||
assertTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
|
||||
try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
|
||||
|
||||
var lock = this;
|
||||
var started = new CountDownLatch(1);
|
||||
var entered = new AtomicBoolean(); // set to true when vthread enters lock
|
||||
|
||||
var vthread1 = factory.newThread(() -> {
|
||||
started.countDown();
|
||||
runWithSynchronizedNative(() -> {
|
||||
assertTrue(Thread.holdsLock(lock));
|
||||
entered.set(true);
|
||||
});
|
||||
assertFalse(Thread.holdsLock(lock));
|
||||
});
|
||||
|
||||
vthread1.start();
|
||||
try {
|
||||
synchronized (this) {
|
||||
// start thread and wait for it to block
|
||||
vthread1.start();
|
||||
started.await();
|
||||
await(vthread1, Thread.State.BLOCKED);
|
||||
|
||||
// carrier should be released, use it for another thread
|
||||
var executed = new AtomicBoolean();
|
||||
var vthread2 = factory.newThread(() -> {
|
||||
executed.set(true);
|
||||
});
|
||||
vthread2.start();
|
||||
vthread2.join();
|
||||
assertTrue(executed.get());
|
||||
}
|
||||
} finally {
|
||||
vthread1.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given task's run method while holding the monitor for "this".
|
||||
*/
|
||||
private synchronized native void runWithSynchronizedNative(Runnable task);
|
||||
|
||||
/**
|
||||
* Invokes the given task's run method while holding the monitor for the given
|
||||
* object. The monitor is entered with JNI MonitorEnter, and exited with JNI MonitorExit.
|
||||
*/
|
||||
private native void runWithMonitorEnteredInNative(Object lock, Runnable task);
|
||||
|
||||
/**
|
||||
* Called from native methods to run the given task.
|
||||
*/
|
||||
private void run(Runnable task) {
|
||||
task.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
2625
test/jdk/java/lang/Thread/virtual/ThreadAPI.java
Normal file
2625
test/jdk/java/lang/Thread/virtual/ThreadAPI.java
Normal file
File diff suppressed because it is too large
Load diff
158
test/jdk/java/lang/Thread/virtual/ThreadLocals.java
Normal file
158
test/jdk/java/lang/Thread/virtual/ThreadLocals.java
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test Virtual threads using thread locals
|
||||
* @library /test/lib
|
||||
* @run junit ThreadLocals
|
||||
*/
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ThreadLocals {
|
||||
static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
|
||||
static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Basic test of thread local set/get.
|
||||
*/
|
||||
@Test
|
||||
void testThreadLocal1() throws Exception {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
VThreadRunner.run(() -> {
|
||||
assertNull(LOCAL.get());
|
||||
Object obj = new Object();
|
||||
LOCAL.set(obj);
|
||||
assertTrue(LOCAL.get() == obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test setting thread local before blocking operation.
|
||||
*/
|
||||
@Test
|
||||
void testThreadLocal2() throws Exception {
|
||||
VThreadRunner.run(() -> {
|
||||
assertNull(LOCAL.get());
|
||||
Object obj = new Object();
|
||||
LOCAL.set(obj);
|
||||
try { Thread.sleep(100); } catch (InterruptedException e) { }
|
||||
assertTrue(LOCAL.get() == obj);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic test of inheritable thread local set/get, no initial value inherited.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal1() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
VThreadRunner.run(() -> {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
Object obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
});
|
||||
}
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inheriting initial value of InheritableThreadLocal from platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal2() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
try {
|
||||
VThreadRunner.run(() -> {
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
});
|
||||
} finally {
|
||||
INHERITED_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test inheriting initial value of InheritableThreadLocal from virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal3() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
VThreadRunner.run(() -> {
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
VThreadRunner.run(() -> {
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
});
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
|
||||
});
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread that does not inherit initial value of InheritableThreadLocals
|
||||
* from platform thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal4() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
try {
|
||||
int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
|
||||
VThreadRunner.run(characteristics, () -> {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
});
|
||||
} finally {
|
||||
INHERITED_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread that does not inherit initial value of InheritableThreadLocals
|
||||
* from virtual thread.
|
||||
*/
|
||||
@Test
|
||||
void testInheritedThreadLocal5() throws Exception {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
VThreadRunner.run(() -> {
|
||||
var obj = new Object();
|
||||
INHERITED_LOCAL.set(obj);
|
||||
int characteristics = VThreadRunner.NO_INHERIT_THREAD_LOCALS;
|
||||
VThreadRunner.run(characteristics, () -> {
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
});
|
||||
assertTrue(INHERITED_LOCAL.get() == obj);
|
||||
|
||||
});
|
||||
assertNull(INHERITED_LOCAL.get());
|
||||
}
|
||||
}
|
||||
83
test/jdk/java/lang/Thread/virtual/ThreadPollOnYield.java
Normal file
83
test/jdk/java/lang/Thread/virtual/ThreadPollOnYield.java
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @bug 8335269
|
||||
* @summary Test that Thread.yield loop polls for safepoints
|
||||
* @requires vm.continuations
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED ThreadPollOnYield
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=c2
|
||||
* @bug 8335269
|
||||
* @summary Test that Thread.yield loop polls for safepoints
|
||||
* @requires vm.continuations & vm.compMode != "Xcomp"
|
||||
* @library /test/lib
|
||||
* @run junit/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED -Xcomp -XX:-TieredCompilation
|
||||
* -XX:CompileCommand=inline,*::yield* -XX:CompileCommand=inline,*::*Yield ThreadPollOnYield
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ThreadPollOnYield {
|
||||
private static final CountDownLatch started = new CountDownLatch(1);
|
||||
static void foo(AtomicBoolean done) {
|
||||
started.countDown();
|
||||
while (!done.get()) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testThreadYieldPolls() throws Exception {
|
||||
AtomicBoolean done = new AtomicBoolean();
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> foo(done));
|
||||
});
|
||||
started.await();
|
||||
done.set(true);
|
||||
vthread.join();
|
||||
|
||||
System.out.println("First vthread done");
|
||||
|
||||
AtomicBoolean done2 = new AtomicBoolean();
|
||||
vthread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> foo(done2));
|
||||
});
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < 5000) {
|
||||
Thread.sleep(250);
|
||||
System.gc();
|
||||
}
|
||||
done2.set(true);
|
||||
vthread.join();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test diagnostic option for detecting a virtual thread using thread locals
|
||||
* @requires vm.continuations
|
||||
* @library /test/lib
|
||||
* @run junit/othervm -Djdk.traceVirtualThreadLocals TraceVirtualThreadLocals
|
||||
* @run junit/othervm -Djdk.traceVirtualThreadLocals=true TraceVirtualThreadLocals
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class TraceVirtualThreadLocals {
|
||||
|
||||
@Test
|
||||
void testInitialValue() throws Exception {
|
||||
String output = run(() -> {
|
||||
ThreadLocal<String> name = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected String initialValue() {
|
||||
return "<unnamed>";
|
||||
}
|
||||
};
|
||||
name.get();
|
||||
});
|
||||
assertContains(output, "java.lang.ThreadLocal.setInitialValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSet() throws Exception {
|
||||
String output = run(() -> {
|
||||
ThreadLocal<String> name = new ThreadLocal<>();
|
||||
name.set("duke");
|
||||
});
|
||||
assertContains(output, "java.lang.ThreadLocal.set");
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task in a virtual thread, returning a String with any output printed
|
||||
* to standard output.
|
||||
*/
|
||||
private static String run(Runnable task) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintStream original = System.out;
|
||||
System.setOut(new PrintStream(baos, true));
|
||||
try {
|
||||
VThreadRunner.run(task::run);
|
||||
} finally {
|
||||
System.setOut(original);
|
||||
}
|
||||
String output = new String(baos.toByteArray());
|
||||
System.out.println(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that s1 contains s2.
|
||||
*/
|
||||
private static void assertContains(String s1, String s2) {
|
||||
assertTrue(s1.contains(s2), s2 + " not found!!!");
|
||||
}
|
||||
}
|
||||
133
test/jdk/java/lang/Thread/virtual/YieldQueuing.java
Normal file
133
test/jdk/java/lang/Thread/virtual/YieldQueuing.java
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Test Thread.yield submits the virtual thread task to the expected queue
|
||||
* @requires vm.continuations
|
||||
* @run junit/othervm -Djdk.virtualThreadScheduler.maxPoolSize=1 YieldQueuing
|
||||
*/
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class YieldQueuing {
|
||||
|
||||
@BeforeAll
|
||||
static void setup() throws Exception {
|
||||
// waiting for LockSupport to be initialized can change the scheduling
|
||||
MethodHandles.lookup().ensureInitialized(LockSupport.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.yield submits the task for the current virtual thread to a scheduler
|
||||
* submission queue when there are no tasks in the local queue.
|
||||
*/
|
||||
@Test
|
||||
void testYieldWithEmptyLocalQueue() throws Exception {
|
||||
var list = new CopyOnWriteArrayList<String>();
|
||||
|
||||
var threadsStarted = new AtomicBoolean();
|
||||
|
||||
var threadA = Thread.ofVirtual().unstarted(() -> {
|
||||
// pin thread until task for B is in submission queue
|
||||
while (!threadsStarted.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
|
||||
list.add("A");
|
||||
Thread.yield(); // push task for A to submission queue, B should run
|
||||
list.add("A");
|
||||
});
|
||||
|
||||
var threadB = Thread.ofVirtual().unstarted(() -> {
|
||||
list.add("B");
|
||||
});
|
||||
|
||||
// push tasks for A and B to submission queue
|
||||
threadA.start();
|
||||
threadB.start();
|
||||
|
||||
// release A
|
||||
threadsStarted.set(true);
|
||||
|
||||
// wait for result
|
||||
threadA.join();
|
||||
threadB.join();
|
||||
assertEquals(list, List.of("A", "B", "A"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Thread.yield submits the task for the current virtual thread to the local
|
||||
* queue when there are tasks in the local queue.
|
||||
*/
|
||||
@Test
|
||||
void testYieldWithNonEmptyLocalQueue() throws Exception {
|
||||
var list = new CopyOnWriteArrayList<String>();
|
||||
|
||||
var threadsStarted = new AtomicBoolean();
|
||||
|
||||
var threadA = Thread.ofVirtual().unstarted(() -> {
|
||||
// pin thread until tasks for B and C are in submission queue
|
||||
while (!threadsStarted.get()) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
|
||||
list.add("A");
|
||||
LockSupport.park(); // B should run
|
||||
list.add("A");
|
||||
});
|
||||
|
||||
var threadB = Thread.ofVirtual().unstarted(() -> {
|
||||
list.add("B");
|
||||
LockSupport.unpark(threadA); // push task for A to local queue
|
||||
Thread.yield(); // push task for B to local queue, A should run
|
||||
list.add("B");
|
||||
});
|
||||
|
||||
var threadC = Thread.ofVirtual().unstarted(() -> {
|
||||
list.add("C");
|
||||
});
|
||||
|
||||
// push tasks for A, B and C to submission queue
|
||||
threadA.start();
|
||||
threadB.start();
|
||||
threadC.start();
|
||||
|
||||
// release A
|
||||
threadsStarted.set(true);
|
||||
|
||||
// wait for result
|
||||
threadA.join();
|
||||
threadB.join();
|
||||
threadC.join();
|
||||
assertEquals(list, List.of("A", "B", "A", "B", "C"));
|
||||
}
|
||||
}
|
||||
43
test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c
Normal file
43
test/jdk/java/lang/Thread/virtual/libSynchronizedNative.c
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_SynchronizedNative_runWithSynchronizedNative(JNIEnv *env, jobject obj, jobject task) {
|
||||
jclass clazz = (*env)->GetObjectClass(env, obj);
|
||||
jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
|
||||
if (mid != NULL) {
|
||||
(*env)->CallVoidMethod(env, obj, mid, task);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_SynchronizedNative_runWithMonitorEnteredInNative(JNIEnv *env, jobject obj, jobject lock, jobject task) {
|
||||
jclass clazz = (*env)->GetObjectClass(env, obj);
|
||||
jmethodID mid = (*env)->GetMethodID(env, clazz, "run", "(Ljava/lang/Runnable;)V");
|
||||
if (mid != NULL && (*env)->MonitorEnter(env, lock) == 0) {
|
||||
(*env)->CallVoidMethod(env, obj, mid, task);
|
||||
(*env)->MonitorExit(env, lock); // can be called with pending exception
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress parking with CompletableFuture timed get
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/othervm -Xmx1g CompletableFutureTimedGet 100000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CompletableFutureTimedGet {
|
||||
|
||||
static final String RESULT = "foo";
|
||||
|
||||
public static void main(String... args) throws InterruptedException {
|
||||
int threadCount = 250_000;
|
||||
if (args.length > 0) {
|
||||
threadCount = Integer.parseInt(args[0]);
|
||||
}
|
||||
|
||||
// the count of the number of threads that complete successfully
|
||||
AtomicInteger completed = new AtomicInteger();
|
||||
|
||||
// list of futures and threads
|
||||
List<CompletableFuture<String>> futures = new ArrayList<>();
|
||||
List<Thread> threads = new ArrayList<>();
|
||||
|
||||
// start threads that wait with timeout for a result
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
var future = new CompletableFuture<String>();
|
||||
futures.add(future);
|
||||
|
||||
// start a thread that uses a timed-get to wait for the result
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
String result = future.get(1, TimeUnit.DAYS);
|
||||
if (!RESULT.equals(result)) {
|
||||
throw new RuntimeException("result=" + result);
|
||||
}
|
||||
completed.incrementAndGet();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// sets the result, which will unpark waiting threads
|
||||
futures.forEach(f -> f.complete(RESULT));
|
||||
|
||||
// wait for all threads to terminate
|
||||
long lastTimestamp = System.currentTimeMillis();
|
||||
boolean done;
|
||||
do {
|
||||
done = true;
|
||||
for (Thread t : threads) {
|
||||
if (!t.join(Duration.ofSeconds(1))) {
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
|
||||
// print trace message so the output tracks progress
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (done || ((currentTime - lastTimestamp) > 500)) {
|
||||
System.out.format("%s => completed %d of %d%n",
|
||||
Instant.now(), completed.get(), threadCount);
|
||||
lastTimestamp = currentTime;
|
||||
}
|
||||
|
||||
} while (!done);
|
||||
|
||||
// all tasks should have completed successfully
|
||||
int completedCount = completed.get();
|
||||
if (completedCount != threadCount) {
|
||||
throw new RuntimeException("completed = " + completedCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.getStackTrace on virtual threads that are blocking or
|
||||
* blocked on monitorenter
|
||||
* @requires vm.debug != true
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 GetStackTraceALotWhenBlocking 10000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 GetStackTraceALotWhenBlocking 5000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import jdk.test.lib.Platform;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
|
||||
public class GetStackTraceALotWhenBlocking {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// need at least two carriers
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
|
||||
int iterations;
|
||||
int value = Integer.parseInt(args[0]);
|
||||
if (Platform.isOSX()) {
|
||||
// reduced iterations on macosx
|
||||
iterations = Math.max(value / 4, 1);
|
||||
} else {
|
||||
iterations = value;
|
||||
}
|
||||
|
||||
var done = new AtomicBoolean();
|
||||
var lock = new Object();
|
||||
|
||||
Runnable task = () -> {
|
||||
long count = 0L;
|
||||
while (!done.get()) {
|
||||
synchronized (lock) {
|
||||
pause();
|
||||
}
|
||||
count++;
|
||||
}
|
||||
System.out.format("%s %s => %d loops%n", Instant.now(), Thread.currentThread(), count);
|
||||
};
|
||||
|
||||
var thread1 = Thread.ofVirtual().start(task);
|
||||
var thread2 = Thread.ofVirtual().start(task);
|
||||
long lastTime = System.nanoTime();
|
||||
try {
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
thread1.getStackTrace();
|
||||
pause();
|
||||
thread2.getStackTrace();
|
||||
pause();
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
if (i == iterations || ((currentTime - lastTime) > 1_000_000_000L)) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
// fail quickly if interrupted by jtreg
|
||||
throw new RuntimeException("interrupted");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
done.set(true);
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
}
|
||||
|
||||
private static void pause() {
|
||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
Thread.onSpinWait();
|
||||
} else {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @summary Stress test asynchronous Thread.getStackTrace when parking
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main GetStackTraceALotWhenParking
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @modules java.base/java.lang:+open
|
||||
* @library /test/lib
|
||||
* @run main/timeout=300 GetStackTraceALotWhenParking 1000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadScheduler;
|
||||
|
||||
public class GetStackTraceALotWhenParking {
|
||||
static class RoundRobinExecutor implements Executor, AutoCloseable {
|
||||
private final ExecutorService[] executors;
|
||||
private int next;
|
||||
|
||||
RoundRobinExecutor() {
|
||||
var factory = Thread.ofPlatform().name("worker-", 1).daemon(true).factory();
|
||||
var executors = new ExecutorService[2];
|
||||
for (int i = 0; i < executors.length; i++) {
|
||||
executors[i] = Executors.newSingleThreadExecutor(factory);
|
||||
}
|
||||
this.executors = executors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
executors[next].execute(task);
|
||||
next = (next + 1) % executors.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
for (int i = 0; i < executors.length; i++) {
|
||||
executors[i].shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 10_000;
|
||||
|
||||
final int ITERATIONS = iterations;
|
||||
final int SPIN_NANOS = 5000;
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
|
||||
try (RoundRobinExecutor executor = new RoundRobinExecutor()) {
|
||||
ThreadFactory factory = VThreadScheduler.virtualThreadFactory(executor);
|
||||
|
||||
Thread thread = factory.newThread(() -> {
|
||||
while (count.incrementAndGet() < ITERATIONS) {
|
||||
long start = System.nanoTime();
|
||||
while ((System.nanoTime() - start) < SPIN_NANOS) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
LockSupport.parkNanos(500_000);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
long start = System.nanoTime();
|
||||
while (thread.isAlive()) {
|
||||
StackTraceElement[] stackTrace = thread.getStackTrace();
|
||||
// printStackTrace(stackTrace);
|
||||
Thread.sleep(5);
|
||||
if ((System.nanoTime() - start) > 500_000_000) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), count.get(), ITERATIONS);
|
||||
start = System.nanoTime();
|
||||
}
|
||||
}
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != ITERATIONS) {
|
||||
throw new RuntimeException("count = " + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void printStackTrace(StackTraceElement[] stackTrace) {
|
||||
if (stackTrace == null) {
|
||||
System.out.println("NULL");
|
||||
} else {
|
||||
for (var e : stackTrace) {
|
||||
System.out.println("\t" + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8322818
|
||||
* @summary Stress test Thread.getStackTrace on a virtual thread that is pinned
|
||||
* @requires vm.debug != true
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 10000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @modules jdk.management
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 5000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.Platform;
|
||||
import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class GetStackTraceALotWhenPinned {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// need at least two carrier threads when main thread is a virtual thread
|
||||
if (Thread.currentThread().isVirtual()) {
|
||||
VThreadRunner.ensureParallelism(2);
|
||||
}
|
||||
|
||||
int iterations;
|
||||
int value = Integer.parseInt(args[0]);
|
||||
if (Platform.isOSX()) {
|
||||
// reduced iterations on macosx
|
||||
iterations = Math.max(value / 4, 1);
|
||||
} else {
|
||||
iterations = value;
|
||||
}
|
||||
|
||||
var barrier = new Barrier(2);
|
||||
|
||||
// Start a virtual thread that loops doing Thread.yield and parking while pinned.
|
||||
// This loop creates the conditions for the main thread to sample the stack trace
|
||||
// as it transitions from being unmounted to parking while pinned.
|
||||
var thread = Thread.startVirtualThread(() -> {
|
||||
boolean timed = false;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
// wait for main thread to arrive
|
||||
barrier.await();
|
||||
|
||||
Thread.yield();
|
||||
boolean b = timed;
|
||||
VThreadPinner.runPinned(() -> {
|
||||
if (b) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
} else {
|
||||
LockSupport.park();
|
||||
}
|
||||
});
|
||||
timed = !timed;
|
||||
}
|
||||
});
|
||||
|
||||
long lastTime = System.nanoTime();
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
// wait for virtual thread to arrive
|
||||
barrier.await();
|
||||
|
||||
thread.getStackTrace();
|
||||
LockSupport.unpark(thread);
|
||||
|
||||
long currentTime = System.nanoTime();
|
||||
if (i == iterations || ((currentTime - lastTime) > 1_000_000_000L)) {
|
||||
System.out.format("%s => %d of %d%n", Instant.now(), i, iterations);
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
// fail quickly if interrupted by jtreg
|
||||
throw new RuntimeException("interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alow threads wait for each other to reach a common barrier point. This class does
|
||||
* not park threads that are waiting for the barrier to trip, instead it spins. This
|
||||
* makes it suitable for tests that use LockSupport.park or Thread.yield.
|
||||
*/
|
||||
private static class Barrier {
|
||||
private final int parties;
|
||||
private final AtomicInteger count;
|
||||
private volatile int generation;
|
||||
|
||||
Barrier(int parties) {
|
||||
this.parties = parties;
|
||||
this.count = new AtomicInteger(parties);
|
||||
}
|
||||
|
||||
void await() {
|
||||
int g = generation;
|
||||
if (count.decrementAndGet() == 0) {
|
||||
count.set(parties);
|
||||
generation = g + 1;
|
||||
} else {
|
||||
while (generation == g) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.getStackTrace on a virtual thread in timed-Object.wait
|
||||
* @requires vm.debug != true
|
||||
* @run main/timeout=480 GetStackTraceALotWithTimedWait 100000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @run main/timeout=480 GetStackTraceALotWithTimedWait 50000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class GetStackTraceALotWithTimedWait {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 100_000;
|
||||
|
||||
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
var done = new AtomicBoolean();
|
||||
var threads = new ArrayList<Thread>();
|
||||
|
||||
// start threads that invoke Object.wait with a short timeout
|
||||
int nthreads = Runtime.getRuntime().availableProcessors();
|
||||
for (int i = 0; i < nthreads; i++) {
|
||||
var ref = new AtomicReference<Thread>();
|
||||
var lock = new Object();
|
||||
executor.submit(() -> {
|
||||
ref.set(Thread.currentThread());
|
||||
while (!done.get()) {
|
||||
synchronized (lock) {
|
||||
int delay = 1 + ThreadLocalRandom.current().nextInt(20);
|
||||
lock.wait(delay);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
Thread thread;
|
||||
while ((thread = ref.get()) == null) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
// hammer on Thread.getStackTrace
|
||||
try {
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
if ((i % 1000) == 0) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + iterations);
|
||||
}
|
||||
for (Thread thread : threads) {
|
||||
thread.getStackTrace();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
done.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual threads entering a lot of monitors with contention
|
||||
* @library /test/lib
|
||||
* @run main/timeout=480 LotsOfContendedMonitorEnter
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class LotsOfContendedMonitorEnter {
|
||||
static int depth;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0) {
|
||||
depth = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
depth = 1024;
|
||||
}
|
||||
|
||||
var exRef = new AtomicReference<Throwable>();
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
testContendedEnter(depth);
|
||||
} catch (Exception e) {
|
||||
exRef.set(e);
|
||||
}
|
||||
});
|
||||
thread.join();
|
||||
assert exRef.get() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the monitor for a new object, racing with another virtual thread that
|
||||
* attempts to enter around the same time, then repeat to the given depth.
|
||||
*/
|
||||
private static void testContendedEnter(int depthRemaining) throws Exception {
|
||||
boolean doLog = depthRemaining % 10 == 0;
|
||||
if (depthRemaining > 0) {
|
||||
var lock = new Object();
|
||||
|
||||
// start thread to enter monitor for brief period, then enters again when signalled
|
||||
var started = new CountDownLatch(1);
|
||||
var signal = new CountDownLatch(1);
|
||||
var thread = Thread.ofVirtual().start(() -> {
|
||||
started.countDown();
|
||||
|
||||
// enter, may be contended
|
||||
synchronized (lock) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
|
||||
// wait to be signalled
|
||||
try {
|
||||
signal.await();
|
||||
} catch (InterruptedException e) { }
|
||||
|
||||
// enter again, this will block until the main thread releases
|
||||
synchronized (lock) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
try {
|
||||
// wait for thread to start
|
||||
started.await();
|
||||
|
||||
// enter, may be contended
|
||||
synchronized (lock) {
|
||||
// signal thread to enter monitor again, it should block
|
||||
signal.countDown();
|
||||
await(thread, Thread.State.BLOCKED);
|
||||
if (doLog) {
|
||||
System.out.println(Instant.now() + " => at depth: " + (depth - depthRemaining));
|
||||
}
|
||||
testContendedEnter(depthRemaining - 1);
|
||||
}
|
||||
} finally {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
if (doLog) {
|
||||
System.out.println(Instant.now() + " => returning from depth: " + (depth - depthRemaining));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
assert state != Thread.State.TERMINATED : "Thread has terminated";
|
||||
Thread.yield();
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Test virtual thread entering (and reentering) a lot of monitors with no contention
|
||||
* @library /test/lib
|
||||
* @run main LotsOfUncontendedMonitorEnter
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import jdk.test.lib.thread.VThreadRunner;
|
||||
|
||||
public class LotsOfUncontendedMonitorEnter {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int depth;
|
||||
if (args.length > 0) {
|
||||
depth = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
depth = 24; // 33554430 enters
|
||||
}
|
||||
VThreadRunner.run(() -> {
|
||||
testEnter(List.of(), depth);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the monitor for a new object, reenter a monitor that is already held, and
|
||||
* repeat to the given depth.
|
||||
*/
|
||||
private static void testEnter(List<Object> ownedMonitors, int depthRemaining) {
|
||||
if (depthRemaining > 0) {
|
||||
var lock = new Object();
|
||||
synchronized (lock) {
|
||||
// new list of owned monitors
|
||||
var monitors = concat(ownedMonitors, lock);
|
||||
testEnter(monitors, depthRemaining - 1);
|
||||
|
||||
// reenter a monitor that is already owned
|
||||
int index = ThreadLocalRandom.current().nextInt(monitors.size());
|
||||
var otherLock = monitors.get(index);
|
||||
|
||||
synchronized (otherLock) {
|
||||
testEnter(monitors, depthRemaining - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to a list, returning a new list.
|
||||
*/
|
||||
private static <T> List<T> concat(List<T> list, T object) {
|
||||
var newList = new ArrayList<>(list);
|
||||
newList.add(object);
|
||||
return newList;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8373120
|
||||
* @summary Stress test two consecutive timed Object.wait calls where only the first one is notified.
|
||||
* @run main/othervm -XX:CompileCommand=exclude,java.lang.VirtualThread::afterYield NotifiedThenTimedOutWait 1 100 100
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @run main/othervm -XX:CompileCommand=exclude,java.lang.VirtualThread::afterYield NotifiedThenTimedOutWait 2 100 100
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Phaser;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class NotifiedThenTimedOutWait {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int race = (args.length > 0) ? Integer.parseInt(args[0]) : 1;
|
||||
int nruns = (args.length > 1) ? Integer.parseInt(args[1]) : 100;
|
||||
int iterations = (args.length > 2) ? Integer.parseInt(args[2]) : 100;
|
||||
|
||||
for (int i = 1; i <= nruns; i++) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + nruns);
|
||||
switch (race) {
|
||||
case 1 -> race1(iterations);
|
||||
case 2 -> race2(iterations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Barrier in synchronized block.
|
||||
*/
|
||||
private static void race1(int iterations) throws InterruptedException {
|
||||
final int timeout = 1;
|
||||
var lock = new Object();
|
||||
var start = new Phaser(2);
|
||||
var end = new Phaser(2);
|
||||
|
||||
var vthread = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
for (int j = 0; j < iterations; j++) {
|
||||
synchronized (lock) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
lock.wait(timeout);
|
||||
lock.wait(timeout);
|
||||
}
|
||||
end.arriveAndAwaitAdvance();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
|
||||
? Thread.ofPlatform().factory() : Thread.ofVirtual().factory();
|
||||
var notifier = factory.newThread(() -> {
|
||||
for (int j = 0; j < iterations; j++) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
end.arriveAndAwaitAdvance();
|
||||
}
|
||||
});
|
||||
notifier.start();
|
||||
|
||||
vthread.join();
|
||||
notifier.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Barrier before synchronized block.
|
||||
*/
|
||||
private static void race2(int iterations) throws InterruptedException {
|
||||
final int timeout = 1;
|
||||
var lock = new Object();
|
||||
var start = new Phaser(2);
|
||||
|
||||
var vthread = Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
synchronized (lock) {
|
||||
lock.wait(timeout);
|
||||
lock.wait(timeout);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
|
||||
? Thread.ofPlatform().factory() : Thread.ofVirtual().factory();
|
||||
var notifier = factory.newThread(() -> {
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
start.arriveAndAwaitAdvance();
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
notifier.start();
|
||||
|
||||
vthread.join();
|
||||
notifier.join();
|
||||
}
|
||||
}
|
||||
114
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
114
test/jdk/java/lang/Thread/virtual/stress/ParkALot.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test parking and unparking
|
||||
* @requires vm.debug != true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 ParkALot 300000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=1200 ParkALot 100000
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.Platform;
|
||||
|
||||
public class ParkALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
int value = Integer.parseInt(args[0]);
|
||||
if (Platform.isOSX()) {
|
||||
// reduced iterations on macosx
|
||||
iterations = Math.max(value / 4, 1);
|
||||
} else {
|
||||
iterations = value;
|
||||
}
|
||||
|
||||
int maxThreads = Math.clamp(Runtime.getRuntime().availableProcessors() / 2, 1, 4);
|
||||
for (int nthreads = 1; nthreads <= maxThreads; nthreads++) {
|
||||
System.out.format("%s %d thread(s) ...%n", Instant.now(), nthreads);
|
||||
ThreadFactory factory = Thread.ofPlatform().factory();
|
||||
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
|
||||
var totalIterations = new AtomicInteger();
|
||||
for (int i = 0; i < nthreads; i++) {
|
||||
executor.submit(() -> parkALot(iterations, totalIterations::incrementAndGet));
|
||||
}
|
||||
|
||||
// shutdown, await for all threads to finish with progress output
|
||||
executor.shutdown();
|
||||
boolean terminated = false;
|
||||
while (!terminated) {
|
||||
terminated = executor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
System.out.format("%s => %d of %d%n",
|
||||
Instant.now(), totalIterations.get(), iterations * nthreads);
|
||||
}
|
||||
}
|
||||
System.out.format("%s %d thread(s) done%n", Instant.now(), nthreads);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a virtual thread that alternates between untimed and timed parking.
|
||||
* A platform thread spins unparking the virtual thread.
|
||||
* @param iterations number of iterations
|
||||
* @param afterIteration the task to run after each iteration
|
||||
*/
|
||||
private static void parkALot(int iterations, Runnable afterIteration) {
|
||||
Thread vthread = Thread.ofVirtual().start(() -> {
|
||||
int i = 0;
|
||||
boolean timed = false;
|
||||
while (i < iterations) {
|
||||
if (timed) {
|
||||
LockSupport.parkNanos(Long.MAX_VALUE);
|
||||
timed = false;
|
||||
} else {
|
||||
LockSupport.park();
|
||||
timed = true;
|
||||
}
|
||||
i++;
|
||||
afterIteration.run();
|
||||
}
|
||||
});
|
||||
|
||||
Thread.State state;
|
||||
while ((state = vthread.getState()) != Thread.State.TERMINATED) {
|
||||
if (state == Thread.State.WAITING || state == Thread.State.TIMED_WAITING) {
|
||||
LockSupport.unpark(vthread);
|
||||
} else {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java
Normal file
120
test/jdk/java/lang/Thread/virtual/stress/ParkAfterTimedPark.java
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=parked
|
||||
* @bug 8369227
|
||||
* @summary Stress test untimed park after a timed park when a thread is unparked around the
|
||||
* same time that the timeout expires.
|
||||
* @library /test/lib
|
||||
* @run main/othervm --enable-native-access=ALL-UNNAMED ParkAfterTimedPark 200 false
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=pinned
|
||||
* @summary Stress test untimed park, while pinned, and after a timed park when a thread is
|
||||
* unparked around the same time that the timeout expires.
|
||||
* @library /test/lib
|
||||
* @run main/othervm --enable-native-access=ALL-UNNAMED ParkAfterTimedPark 200 true
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class ParkAfterTimedPark {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 100;
|
||||
boolean pinned = (args.length > 1) ? Boolean.parseBoolean(args[1]) : false;
|
||||
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + iterations);
|
||||
for (int timeout = 1; timeout <= 10; timeout++) {
|
||||
test(timeout, pinned);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates two virtual threads. The first does a timed-park for the given time,
|
||||
* then parks in CountDownLatch.await. A second virtual thread unparks the first
|
||||
* around the same time that the timeout for the first expires.
|
||||
*/
|
||||
private static void test(int millis, boolean pinned) throws Exception {
|
||||
long nanos = TimeUnit.MILLISECONDS.toNanos(millis);
|
||||
|
||||
var finish = new CountDownLatch(1);
|
||||
|
||||
Thread thread1 = Thread.startVirtualThread(() -> {
|
||||
LockSupport.parkNanos(nanos);
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
try {
|
||||
if (pinned) {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
finish.await();
|
||||
});
|
||||
} else {
|
||||
finish.await();
|
||||
}
|
||||
done = true;
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
});
|
||||
|
||||
Thread thread2 = Thread.startVirtualThread(() -> {
|
||||
int delta = ThreadLocalRandom.current().nextInt(millis);
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
try {
|
||||
Thread.sleep(millis - delta);
|
||||
done = true;
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
LockSupport.unpark(thread1);
|
||||
});
|
||||
|
||||
// wait for first thread to park before count down
|
||||
await(thread1, Thread.State.WAITING);
|
||||
finish.countDown();
|
||||
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the given thread to reach a given state.
|
||||
*/
|
||||
private static void await(Thread thread, Thread.State expectedState) throws Exception {
|
||||
Thread.State state = thread.getState();
|
||||
while (state != expectedState) {
|
||||
if (state == Thread.State.TERMINATED)
|
||||
throw new RuntimeException("Thread has terminated");
|
||||
Thread.sleep(10);
|
||||
state = thread.getState();
|
||||
}
|
||||
}
|
||||
}
|
||||
76
test/jdk/java/lang/Thread/virtual/stress/PinALot.java
Normal file
76
test/jdk/java/lang/Thread/virtual/stress/PinALot.java
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test timed park when pinned
|
||||
* @requires vm.debug != true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=480 --enable-native-access=ALL-UNNAMED PinALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @library /test/lib
|
||||
* @run main/othervm/native/timeout=1200 --enable-native-access=ALL-UNNAMED PinALot 200000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import jdk.test.lib.thread.VThreadPinner;
|
||||
|
||||
public class PinALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
VThreadPinner.runPinned(() -> {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
LockSupport.parkNanos(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
test/jdk/java/lang/Thread/virtual/stress/PingPong.java
Normal file
131
test/jdk/java/lang/Thread/virtual/stress/PingPong.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=sq
|
||||
* @summary Stress test virtual threads with a SynchronousQueue
|
||||
* @requires vm.debug != true
|
||||
* @run main PingPong SQ 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=ltq
|
||||
* @summary Stress test virtual threads with a LinkedTransferQueue
|
||||
* @requires vm.debug != true
|
||||
* @run main PingPong LTQ 500000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class PingPong {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Exchanger<String> queue;
|
||||
int iterations;
|
||||
if (args.length == 0) {
|
||||
queue = new LTQExchanger<>();
|
||||
iterations = 10_000_000;
|
||||
} else {
|
||||
if (args[0].equals("SQ")) {
|
||||
queue = new SQExchanger<>();
|
||||
} else {
|
||||
queue = new LTQExchanger<>();
|
||||
}
|
||||
iterations = Integer.parseInt(args[1]);
|
||||
}
|
||||
|
||||
AtomicInteger count1 = new AtomicInteger();
|
||||
AtomicInteger count2 = new AtomicInteger();
|
||||
|
||||
Thread t1 = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
while (count1.incrementAndGet() < iterations) {
|
||||
queue.transfer("hello");
|
||||
String reply = queue.take();
|
||||
if (!"ack".equals(reply)) {
|
||||
throw new RuntimeException("reply=" + reply);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
Thread t2 = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
while (count2.incrementAndGet() < iterations) {
|
||||
String message = queue.take();
|
||||
if (!"hello".equals(message)) {
|
||||
throw new RuntimeException("message=" + message);
|
||||
}
|
||||
queue.transfer("ack");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = t1.join(Duration.ofMillis(500));
|
||||
if (terminated) {
|
||||
terminated = t2.join(Duration.ofMillis(500));
|
||||
}
|
||||
System.out.format("%s => T1 %d of %d, T2 %d of %d%n",
|
||||
Instant.now(), count1.get(), iterations, count2.get(), iterations);
|
||||
} while (!terminated);
|
||||
}
|
||||
|
||||
interface Exchanger<E> {
|
||||
void transfer (E value) throws InterruptedException;
|
||||
E take() throws InterruptedException;
|
||||
}
|
||||
|
||||
static class SQExchanger<E> implements Exchanger<E> {
|
||||
private final SynchronousQueue<E> queue = new SynchronousQueue<>();
|
||||
@Override
|
||||
public void transfer(E value) throws InterruptedException {
|
||||
queue.put(value);
|
||||
}
|
||||
@Override
|
||||
public E take() throws InterruptedException {
|
||||
return queue.take();
|
||||
}
|
||||
}
|
||||
|
||||
static class LTQExchanger<E> implements Exchanger<E> {
|
||||
private final LinkedTransferQueue<E> queue = new LinkedTransferQueue<>();
|
||||
@Override
|
||||
public void transfer(E value) throws InterruptedException {
|
||||
queue.transfer(value);
|
||||
}
|
||||
@Override
|
||||
public E take() throws InterruptedException {
|
||||
return queue.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
119
test/jdk/java/lang/Thread/virtual/stress/Skynet.java
Normal file
119
test/jdk/java/lang/Thread/virtual/stress/Skynet.java
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=default
|
||||
* @summary Stress test virtual threads with a variation of the Skynet 1M benchmark
|
||||
* @requires vm.continuations
|
||||
* @requires !vm.debug | vm.gc != "Z"
|
||||
* @run main/othervm/timeout=1600 -Xmx1500m Skynet
|
||||
*/
|
||||
/*
|
||||
* @test id=Z
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @requires vm.gc.Z
|
||||
* @run main/othervm/timeout=1600 -XX:+UnlockDiagnosticVMOptions
|
||||
* -XX:+UseZGC
|
||||
* -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1500m Skynet
|
||||
*/
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class Skynet {
|
||||
public static void main(String[] args) {
|
||||
int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 10;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
skynet(1_000_000, 499999500000L);
|
||||
}
|
||||
}
|
||||
|
||||
static void skynet(int num, long expected) {
|
||||
long start = System.currentTimeMillis();
|
||||
var chan = new Channel<Long>();
|
||||
|
||||
Thread.startVirtualThread(() -> skynet(chan, 0, num, 10));
|
||||
|
||||
long sum = chan.receive();
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.format("Result: %d in %s ms%n", sum, (end-start));
|
||||
if (sum != expected)
|
||||
throw new RuntimeException("Expected " + expected);
|
||||
}
|
||||
|
||||
static void skynet(Channel<Long> result, int num, int size, int div) {
|
||||
if (size == 1) {
|
||||
result.send((long)num);
|
||||
} else {
|
||||
var chan = new Channel<Long>();
|
||||
for (int i = 0; i < div; i++) {
|
||||
int subNum = num + i * (size / div);
|
||||
Thread.startVirtualThread(() -> skynet(chan, subNum, size / div, div));
|
||||
}
|
||||
long sum = 0;
|
||||
for (int i = 0; i < div; i++) {
|
||||
sum += chan.receive();
|
||||
}
|
||||
result.send(sum);
|
||||
}
|
||||
}
|
||||
|
||||
static class Channel<T> {
|
||||
private final BlockingQueue<T> q;
|
||||
|
||||
Channel() {
|
||||
q = new SynchronousQueue<>();
|
||||
}
|
||||
|
||||
void send(T e) {
|
||||
boolean interrupted = false;
|
||||
while (true) {
|
||||
try {
|
||||
q.put(e);
|
||||
break;
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
T receive() {
|
||||
boolean interrupted = false;
|
||||
T e;
|
||||
while (true) {
|
||||
try {
|
||||
e = q.take();
|
||||
break;
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test virtual threads with a variation of the Skynet 1M benchmark that uses
|
||||
* a channel implementation based on object monitors. This variant uses a reduced number of
|
||||
* 100k virtual threads at the final level.
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/othervm/timeout=1200 Skynet100kWithMonitors 50
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @run main/othervm/timeout=1200 Skynet100kWithMonitors 10
|
||||
*/
|
||||
|
||||
public class Skynet100kWithMonitors {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int iterations = (args.length) > 0 ? Integer.parseInt(args[0]) : 10;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
skynet(100_000, 4999950000L);
|
||||
}
|
||||
}
|
||||
|
||||
static void skynet(int num, long expected) {
|
||||
long start = System.currentTimeMillis();
|
||||
var chan = new Channel<Long>();
|
||||
|
||||
Thread.startVirtualThread(() -> skynet(chan, 0, num, 10));
|
||||
|
||||
long sum = chan.receive();
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.format("Result: %d in %s ms%n", sum, (end-start));
|
||||
if (sum != expected)
|
||||
throw new RuntimeException("Expected " + expected);
|
||||
}
|
||||
|
||||
static void skynet(Channel<Long> result, int num, int size, int div) {
|
||||
if (size == 1) {
|
||||
result.send((long)num);
|
||||
} else {
|
||||
var chan = new Channel<Long>();
|
||||
for (int i = 0; i < div; i++) {
|
||||
int subNum = num + i * (size / div);
|
||||
Thread.startVirtualThread(() -> skynet(chan, subNum, size / div, div));
|
||||
}
|
||||
long sum = 0;
|
||||
for (int i = 0; i < div; i++) {
|
||||
sum += chan.receive();
|
||||
}
|
||||
result.send(sum);
|
||||
}
|
||||
}
|
||||
|
||||
static class Channel<T> {
|
||||
private final Object lock = new Object();
|
||||
private T element;
|
||||
|
||||
Channel() {
|
||||
}
|
||||
|
||||
void send(T e) {
|
||||
boolean interrupted = false;
|
||||
synchronized (lock) {
|
||||
while (element != null) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
element = e;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
T receive() {
|
||||
T e;
|
||||
boolean interrupted = false;
|
||||
synchronized (lock) {
|
||||
while ((e = element) == null) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException x) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
element = null;
|
||||
lock.notifyAll();
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
test/jdk/java/lang/Thread/virtual/stress/SleepALot.java
Normal file
71
test/jdk/java/lang/Thread/virtual/stress/SleepALot.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.sleep
|
||||
* @requires vm.debug != true & vm.continuations
|
||||
* @run main/timeout=480 SleepALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true & vm.continuations
|
||||
* @run main/othervm/timeout=1200 SleepALot 200000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class SleepALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
try {
|
||||
Thread.sleep(Duration.ofNanos(100));
|
||||
} catch (InterruptedException ignore) { }
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
132
test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java
Normal file
132
test/jdk/java/lang/Thread/virtual/stress/TimedWaitALot.java
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout
|
||||
* @summary Stress test timed-Object.wait
|
||||
* @run main TimedWaitALot 200
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout-notify
|
||||
* @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
|
||||
* at around the same time that the timeout expires.
|
||||
* @run main TimedWaitALot 150 true false
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout-interrupt
|
||||
* @summary Test timed-Object.wait where the waiting thread is awakened with Thread.interrupt
|
||||
* at around the same time that the timeout expires.
|
||||
* @run main TimedWaitALot 150 false true
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=timeout-notify-interrupt
|
||||
* @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
|
||||
* and Thread.interrupt at around the same time that the timeout expires.
|
||||
* @run main TimedWaitALot 100 true true
|
||||
*/
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class TimedWaitALot {
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations = Integer.parseInt(args[0]);
|
||||
boolean notify = args.length >= 2 && "true".equals(args[1]);
|
||||
boolean interrupt = args.length >=3 && "true".equals(args[2]);
|
||||
|
||||
// test all timeouts concurrently
|
||||
int[] timeouts = { 10, 20, 50, 100 };
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
System.out.println(Instant.now() + " => " + i + " of " + iterations);
|
||||
test(notify, interrupt, timeouts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a first virtual thread to wait in Object.wait(millis).
|
||||
* If {@code notify} is true, start a virtual thread to use Object.notifyAll at around
|
||||
* the same time that the timeout expires.
|
||||
* If {@code interrupt} is true, start virtual thread to interrupts the first virtual
|
||||
* thread at around the same time as the timeout expires.
|
||||
*/
|
||||
static void test(boolean notify, boolean interrupt, int... timeouts) throws Exception {
|
||||
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
for (int timeout : timeouts) {
|
||||
var queue = new SynchronousQueue<Thread>();
|
||||
var lock = new Object();
|
||||
|
||||
// virtual thread waits with Object.wait(timeout)
|
||||
executor.submit(() -> {
|
||||
queue.put(Thread.currentThread());
|
||||
synchronized (lock) {
|
||||
lock.wait(timeout);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// wait for thread to start
|
||||
Thread thread = queue.take();
|
||||
|
||||
// start thread to Object.notifyAll at around time that the timeout expires
|
||||
if (notify) {
|
||||
executor.submit(() -> {
|
||||
if (ThreadLocalRandom.current().nextBoolean()) {
|
||||
synchronized (lock) {
|
||||
sleepLessThan(timeout);
|
||||
lock.notifyAll();
|
||||
}
|
||||
} else {
|
||||
sleepLessThan(timeout);
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// start thread to interrupt first thread at around time that the timeout expires
|
||||
if (interrupt) {
|
||||
executor.submit(() -> {
|
||||
sleepLessThan(timeout);
|
||||
thread.interrupt();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for just less than the given timeout, in millis.
|
||||
*/
|
||||
private static void sleepLessThan(long timeout) throws InterruptedException {
|
||||
int delta = ThreadLocalRandom.current().nextInt(10);
|
||||
Thread.sleep(timeout - delta);
|
||||
}
|
||||
}
|
||||
69
test/jdk/java/lang/Thread/virtual/stress/YieldALot.java
Normal file
69
test/jdk/java/lang/Thread/virtual/stress/YieldALot.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Stress test Thread.yield
|
||||
* @requires vm.debug != true
|
||||
* @run main YieldALot 500000
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @requires vm.debug == true
|
||||
* @run main YieldALot 200000
|
||||
*/
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class YieldALot {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
int iterations;
|
||||
if (args.length > 0) {
|
||||
iterations = Integer.parseInt(args[0]);
|
||||
} else {
|
||||
iterations = 1_000_000;
|
||||
}
|
||||
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
Thread thread = Thread.ofVirtual().start(() -> {
|
||||
while (count.incrementAndGet() < iterations) {
|
||||
Thread.yield();
|
||||
}
|
||||
});
|
||||
|
||||
boolean terminated;
|
||||
do {
|
||||
terminated = thread.join(Duration.ofSeconds(1));
|
||||
System.out.println(Instant.now() + " => " + count.get() + " of " + iterations);
|
||||
} while (!terminated);
|
||||
|
||||
int countValue = count.get();
|
||||
if (countValue != iterations) {
|
||||
throw new RuntimeException("Thread terminated, count=" + countValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
test/jdk/java/lang/Thread/virtual/stress/skynet.md
Normal file
25
test/jdk/java/lang/Thread/virtual/stress/skynet.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
## Skynet f341a13
|
||||
|
||||
### MIT License
|
||||
|
||||
```
|
||||
Copyright (c) 2022 Alexander Temerev and contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue