8892d45b9f
Reviewed-by: cjplummer, sspitsyn
247 lines
10 KiB
Java
247 lines
10 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* @test id=default
|
|
* @requires vm.jvmti
|
|
* @requires vm.continuations
|
|
* @run main/othervm/native
|
|
* -Djdk.virtualThreadScheduler.maxPoolSize=1
|
|
* -agentlib:VThreadStackRefTest
|
|
* VThreadStackRefTest
|
|
*/
|
|
|
|
/**
|
|
* @test id=no-vmcontinuations
|
|
* @requires vm.jvmti
|
|
* @run main/othervm/native
|
|
* -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
|
|
* -agentlib:VThreadStackRefTest
|
|
* VThreadStackRefTest NoMountCheck
|
|
*/
|
|
|
|
import java.lang.ref.Reference;
|
|
import java.util.stream.Stream;
|
|
import java.util.concurrent.CountDownLatch;
|
|
|
|
/*
|
|
* The test verifies JVMTI FollowReferences function reports references from
|
|
* mounted and unmounted virtual threads and reports correct thread id
|
|
* (for mounted vthread it should be vthread id, and not carrier thread id).
|
|
* Additionally tests that references from platform threads are reported correctly
|
|
* and that references from terminated vthread are not reported.
|
|
* To get both mounted and unmounted vthreads the test:
|
|
* - limits the number of carrier threads to 1;
|
|
* - starts vthread that creates a stack local and JNI local
|
|
* and then waits in CountDownLatch.await();
|
|
* - starts another vthread that create stack local and JNI local (on top frame)
|
|
* and waits in native to avoid unmounting.
|
|
*/
|
|
public class VThreadStackRefTest {
|
|
|
|
// Currently we cannot test JNI locals for unmounted threads
|
|
// as native calls pin virtual thread.
|
|
// TODO: revise if this changes.
|
|
static final boolean testUnmountedJNILocals = false;
|
|
|
|
// The flag is set by createObjAndWait method.
|
|
static volatile boolean mountedVthreadReady = false;
|
|
|
|
public static void main(String[] args) throws InterruptedException {
|
|
boolean noMountCheck = args.length > 0
|
|
&& args[0].equalsIgnoreCase("NoMountCheck");
|
|
CountDownLatch dumpedLatch = new CountDownLatch(1);
|
|
|
|
CountDownLatch unmountedThreadReady = new CountDownLatch(1);
|
|
// Unmounted virtual thread with stack local.
|
|
Thread vthreadUnmounted = Thread.ofVirtual().start(() -> {
|
|
Object referenced = new VThreadUnmountedReferenced();
|
|
System.out.println("created " + referenced.getClass());
|
|
if (testUnmountedJNILocals) {
|
|
createObjAndCallback(VThreadUnmountedJNIReferenced.class,
|
|
new Runnable() {
|
|
public void run() {
|
|
unmountedThreadReady.countDown();
|
|
await(dumpedLatch);
|
|
}
|
|
});
|
|
} else {
|
|
unmountedThreadReady.countDown();
|
|
await(dumpedLatch);
|
|
}
|
|
Reference.reachabilityFence(referenced);
|
|
});
|
|
// Wait until unmounted thread is ready.
|
|
unmountedThreadReady.await();
|
|
|
|
// Ended virtual thread with stack local - should not be reported.
|
|
Thread vthreadEnded = Thread.ofVirtual().start(() -> {
|
|
Object referenced = new VThreadUnmountedEnded();
|
|
System.out.println("created " + referenced.getClass());
|
|
Reference.reachabilityFence(referenced);
|
|
});
|
|
// Make sure this vthread has exited so we can test
|
|
// that it no longer holds any stack references.
|
|
vthreadEnded.join();
|
|
|
|
// Mounted virtual thread with stack local and JNI local on top frame.
|
|
Thread vthreadMounted = Thread.ofVirtual().start(() -> {
|
|
Object referenced = new VThreadMountedReferenced();
|
|
System.out.println("created " + referenced.getClass());
|
|
createObjAndWait(VThreadMountedJNIReferenced.class);
|
|
Reference.reachabilityFence(referenced);
|
|
});
|
|
// Wait until mounted vthread is ready.
|
|
while (!mountedVthreadReady) {
|
|
Thread.sleep(10);
|
|
}
|
|
|
|
CountDownLatch pThreadReady = new CountDownLatch(1);
|
|
// Sanity check - reference from platform thread stack.
|
|
Thread pthread = Thread.ofPlatform().start(() -> {
|
|
Object referenced = new PThreadReferenced();
|
|
System.out.println("created " + referenced.getClass());
|
|
pThreadReady.countDown();
|
|
await(dumpedLatch);
|
|
Reference.reachabilityFence(referenced);
|
|
});
|
|
// Wait until platform thread is ready.
|
|
pThreadReady.await();
|
|
|
|
System.out.println("threads:");
|
|
System.out.println(" - vthreadUnmounted: " + vthreadUnmounted);
|
|
System.out.println(" - vthreadEnded: " + vthreadEnded);
|
|
System.out.println(" - vthreadMounted: " + vthreadMounted);
|
|
System.out.println(" - pthread: " + pthread);
|
|
|
|
TestCase[] testCases = new TestCase[] {
|
|
new TestCase(VThreadUnmountedReferenced.class, 1, vthreadUnmounted.getId()),
|
|
new TestCase(VThreadUnmountedJNIReferenced.class,
|
|
testUnmountedJNILocals ? 1 : 0,
|
|
testUnmountedJNILocals ? vthreadUnmounted.getId() : 0),
|
|
new TestCase(VThreadMountedReferenced.class, 1, vthreadMounted.getId()),
|
|
new TestCase(VThreadMountedJNIReferenced.class, 1, vthreadMounted.getId()),
|
|
new TestCase(PThreadReferenced.class, 1, pthread.getId()),
|
|
// expected to be unreported as stack local
|
|
new TestCase(VThreadUnmountedEnded.class, 0, 0)
|
|
};
|
|
|
|
Class[] testClasses = Stream.of(testCases).map(c -> c.cls()).toArray(Class[]::new);
|
|
System.out.println("test classes:");
|
|
for (int i = 0; i < testClasses.length; i++) {
|
|
System.out.println(" (" + i + ") " + testClasses[i]);
|
|
}
|
|
|
|
try {
|
|
if (noMountCheck) {
|
|
System.out.println("INFO: No mount/unmount checks");
|
|
} else {
|
|
verifyVthreadMounted(vthreadUnmounted, false);
|
|
verifyVthreadMounted(vthreadMounted, true);
|
|
}
|
|
|
|
test(testClasses);
|
|
} finally {
|
|
// Finish all threads
|
|
endWait(); // signal mounted vthread to exit
|
|
dumpedLatch.countDown(); // signal unmounted vthread and platform thread to exit
|
|
}
|
|
|
|
vthreadMounted.join();
|
|
vthreadUnmounted.join();
|
|
pthread.join();
|
|
|
|
boolean failed = false;
|
|
for (int i = 0; i < testCases.length; i++) {
|
|
int refCount = getRefCount(i);
|
|
long threadId = getRefThreadID(i);
|
|
String status = "OK";
|
|
if (refCount != testCases[i].expectedCount()
|
|
|| threadId != testCases[i].expectedThreadId()) {
|
|
failed = true;
|
|
status = "ERROR";
|
|
}
|
|
System.out.println(" (" + i + ") " + status
|
|
+ " " + testCases[i].cls()
|
|
+ ": ref count = " + refCount
|
|
+ " (expected " + testCases[i].expectedCount() + ")"
|
|
+ ", thread id = " + threadId
|
|
+ " (expected " + testCases[i].expectedThreadId() + ")");
|
|
}
|
|
if (failed) {
|
|
throw new RuntimeException("Test failed");
|
|
}
|
|
}
|
|
|
|
private static void await(CountDownLatch dumpedLatch) {
|
|
try {
|
|
dumpedLatch.await();
|
|
} catch (InterruptedException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static void verifyVthreadMounted(Thread t, boolean expectedMounted) {
|
|
// Hacky, but simple.
|
|
// If virtual thread is mounted, its toString() contains
|
|
// info about carrier thread, something like
|
|
// VirtualThread[#27]/runnable@ForkJoinPool-1-worker-1
|
|
String s = t.toString();
|
|
boolean mounted = t.isVirtual() && s.contains("/runnable@");
|
|
System.out.println("Thread " + t + ": " + (mounted ? "mounted" : "unmounted"));
|
|
if (mounted != expectedMounted) {
|
|
throw new RuntimeException("Thread " + t + " has unexpected mount state");
|
|
}
|
|
}
|
|
|
|
private static native void test(Class<?>... classes);
|
|
private static native int getRefCount(int index);
|
|
private static native long getRefThreadID(int index);
|
|
|
|
// Creates object of the the specified class (local JNI)
|
|
// and calls the provided callback.
|
|
private static native void createObjAndCallback(Class cls, Runnable callback);
|
|
// Creates object of the the specified class (local JNI),
|
|
// sets mountedVthreadReady static field,
|
|
// and then waits until endWait() method is called.
|
|
private static native void createObjAndWait(Class cls);
|
|
// Signals createObjAndWait() to exit.
|
|
private static native void endWait();
|
|
|
|
private record TestCase(Class cls, int expectedCount, long expectedThreadId) {
|
|
}
|
|
|
|
public static class VThreadUnmountedReferenced {
|
|
}
|
|
public static class VThreadUnmountedJNIReferenced {
|
|
}
|
|
public static class VThreadUnmountedEnded {
|
|
}
|
|
public static class VThreadMountedReferenced {
|
|
}
|
|
public static class VThreadMountedJNIReferenced {
|
|
}
|
|
public static class PThreadReferenced {
|
|
}
|
|
}
|