jdk/test/jdk/java/lang/Thread/virtual/KlassInit.java
Patricio Chilano Mateo 286f7bbc81 8385655: Timeout in java/lang/Thread/virtual/KlassInit.java
Reviewed-by: alanb, shade
2026-06-08 16:35:37 +00:00

500 lines
17 KiB
Java

/*
* 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 jdk.test.lib.thread.VThreadRunner;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
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);
@BeforeAll
static void setup() {
// need >=2 carriers for testing pinning
VThreadRunner.ensureParallelism(2);
}
/**
* 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();
}
}
}