JDK-8345543 : Test serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java failed: expected JVMTI_ERROR_OPAQUE_FRAME instead of: 0
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 24
  • Priority: P4
  • Status: In Progress
  • Resolution: Unresolved
  • Submitted: 2024-12-04
  • Updated: 2025-01-17
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 25
25Unresolved
Related Reports
Relates :  
Relates :  
Relates :  
Description
----------System.out:(17/508)----------
Agent init
Main: prepareAgent started
Main: prepareAgent finished

Main #A: method A() must be blocked on entering a synchronized statement
TestTask.run: started
TestTask.A: started

Main #A.1: unsuspended
Main: stopThread: StopThread returned code: JVMTI_ERROR_THREAD_NOT_SUSPENDED (13)
Main #A.1: got expected THREAD_NOT_SUSPENDED

Main #A.2: suspended
Main: suspendThread
Main: stopThread: StopThread returned code: JVMTI_ERROR_NONE (0)

FAILED: Main #A.2: expected JVMTI_ERROR_OPAQUE_FRAME instead of: 0
----------System.err:(13/725)----------
java.lang.RuntimeException: StopThreadTest failed!
	at StopThreadTest.throwFailed(StopThreadTest.java:89)
	at StopThreadTest.run(StopThreadTest.java:134)
	at StopThreadTest.main(StopThreadTest.java:94)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at com.sun.javatest.regtest.agent.MainWrapper$MainTask.run(MainWrapper.java:138)
	at java.base/java.lang.Thread.run(Thread.java:1447)

This appears to be what was expected prior to the fix for JDK-8307968
Comments
A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/23125 Date: 2025-01-15 07:48:07 +0000
15-01-2025

I think that’s the simplest solution. I thought we wanted to be in the BLOCKED state already while posting the monitor_contended_enter event, but if not then I agree we should just move it as suggested. Same fix could be done for 8346792.
13-01-2025

The suggested fix is to move JavaThreadBlockedOnMonitorEnterState after block for virtual threads with the try_preempt call: diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp index 0f288baf0dd..cc67e5d7201 100644 --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -492,9 +492,7 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread *current, ObjectMonito freeze_result result; - { // Change java thread status to indicate blocked on monitor enter. - JavaThreadBlockedOnMonitorEnterState jtbmes(current, this); - + { assert(current->current_pending_monitor() == nullptr, "invariant"); current->set_current_pending_monitor(this); @@ -533,6 +531,9 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread *current, ObjectMonito } } + // Change java thread status to indicate blocked on monitor enter. + JavaThreadBlockedOnMonitorEnterState jtbmes(current, this); + OSThreadContendState osts(current->osthread()); assert(current->thread_state() == _thread_in_vm, "invariant");
11-01-2025

> The issue can be easily reproduced by adding a delay between JavaThreadBlockedOnMonitorEnterState and the call to try_preempt(). Thanks, Patricio! With the suggested delay this issue is ~100% reproducible: git diff src/hotspot/share/runtime/objectMonitor.cpp diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp index 0f288baf0dd..69bd4cda9f2 100644 --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -511,6 +511,7 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread *current, ObjectMonito ContinuationEntry* ce = current->last_continuation(); if (ce != nullptr && ce->is_virtual_thread()) { + os::naked_short_sleep(10); // DBG-TMP result = Continuation::try_preempt(current, ce->cont_oop(current)); if (result == freeze_ok) { bool acquired = VThreadMonitorEnter(current);
11-01-2025

Thank you, Patricio! This explains the root cause.
26-12-2024

I was able to reproduce it too. The vthread is getting suspended in try_preempt() while calling JvmtiVTMSTransitionDisabler::start_VTMS_transition(). Here is the stacktrace of the vthread/carrier on failure: "ForkJoinPool-1-worker-1" #37 daemon prio=5 tid=0x0000fffbf803b930 nid=1812952 waiting on condition [0x0000fffc194ed000] java.lang.Thread.State: BLOCKED (on object monitor) JavaThread state: _thread_blocked 0x0000fffc7670e134 __pthread_cond_timedwait + 0x2fc 0x0000fffc7594359c PlatformMonitor::wait(unsigned long) + 0xa8 0x0000fffc75895820 Monitor::wait(unsigned long) + 0x120 0x0000fffc7566c85c JvmtiVTMSTransitionDisabler::start_VTMS_transition(_jobject*, bool) + 0x2ec 0x0000fffc7566dc54 JvmtiVTMSTransitionDisabler::VTMS_vthread_unmount(_jobject*, bool) + 0x64 0x0000fffc74f1d038 Continuation::try_preempt(JavaThread*, oop) + 0x174 0x0000fffc758eeb1c ObjectMonitor::enter_with_contention_mark(JavaThread*, ObjectMonitorContentionMark&) + 0x56c 0x0000fffc758ef64c ObjectMonitor::enter(JavaThread*) + 0x8c 0x0000fffc756ce540 LightweightSynchronizer::inflate_and_enter(oop, ObjectSynchronizer::InflateCause, JavaThread*, JavaThread*) + 0x4ac 0x0000fffc756cf0a0 LightweightSynchronizer::enter(Handle, BasicLock*, JavaThread*) + 0x2c0 0x0000fffc75304a20 InterpreterRuntime::monitorenter(JavaThread*, BasicObjectLock*) + 0x1b0 0x0000fffc5f23dcec * StopThreadTest$TestTask.A() bci:15 line:258 (Interpreted frame) 0x0000fffc5f2252d0 * StopThreadTest$TestTask.run() bci:7 line:212 (Interpreted frame) 0x0000fffc5f225de8 * java.lang.Thread.runWith(java.lang.Object, java.lang.Runnable) bci:5 line:1460 (Interpreted frame) 0x0000fffc5f2252d0 * java.lang.VirtualThread.run(java.lang.Runnable) bci:62 line:466 (Interpreted frame) 0x0000fffc5f2252d0 * java.lang.VirtualThread$VThreadContinuation$1.run() bci:15 line:258 (Interpreted frame) 0x0000fffc5f225de8 * jdk.internal.vm.Continuation.enter0() bci:4 line:325 (Interpreted frame) 0x0000fffc5f2252d0 * jdk.internal.vm.Continuation.enter(jdk.internal.vm.Continuation, boolean) bci:1 line:316 (Interpreted frame) 0x0000fffc5f9605d8 jdk.internal.vm.Continuation.enterSpecial(jdk.internal.vm.Continuation, boolean, boolean) + 0x118 (Native method) 0x0000fffc5f2252d0 * jdk.internal.vm.Continuation.run() bci:122 line:251 (Interpreted frame) 0x0000fffc5f2252d0 * java.lang.VirtualThread.runContinuation() bci:100 line:303 (Interpreted frame) 0x0000fffc5f2252d0 * java.lang.VirtualThread$$Lambda+0x000001f001047ea0.run() bci:4 (Interpreted frame) 0x0000fffc5f225de8 * java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute() bci:4 line:1735 (Interpreted frame) 0x0000fffc5f224e18 * java.util.concurrent.ForkJoinTask$RunnableExecuteAction.compute() bci:1 line:1726 (Interpreted frame) 0x0000fffc5f224e18 * java.util.concurrent.ForkJoinTask$InterruptibleTask.exec() bci:51 line:1650 (Interpreted frame) 0x0000fffc5f224f80 * java.util.concurrent.ForkJoinTask.doExec() bci:10 line:507 (Interpreted frame) 0x0000fffc5f2252d0 * java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(java.util.concurrent.ForkJoinTask, int) bci:5 line:1394 (Interpreted frame) 0x0000fffc5f2252d0 * java.util.concurrent.ForkJoinPool.runWorker(java.util.concurrent.ForkJoinPool$WorkQueue) bci:362 line:1970 (Interpreted frame) 0x0000fffc5f2252d0 * java.util.concurrent.ForkJoinWorkerThread.run() bci:31 line:187 (Interpreted frame) 0x0000fffc5f220190 <StubRoutines> 0x0000fffc75326224 JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*) + 0x444 0x0000fffc75326868 JavaCalls::call_virtual(JavaValue*, Klass*, Symbol*, Symbol*, JavaCallArguments*, JavaThread*) + 0x268 0x0000fffc75326dec JavaCalls::call_virtual(JavaValue*, Handle, Klass*, Symbol*, Symbol*, JavaThread*) + 0x6c 0x0000fffc75489854 thread_entry(JavaThread*, JavaThread*) + 0xa0 0x0000fffc75360604 JavaThread::thread_main_inner() + 0xe4 0x0000fffc75c00330 Thread::call_run() + 0xac 0x0000fffc759331a4 thread_native_entry(Thread*) + 0x130 0x0000fffc76707950 start_thread + 0x190 The reason why TestTask.ensureBlockedAfterPointA() previously succeeded is because when the vthread state is RUNNING we return the Thread.State from the carrier, which changes to BLOCKED when executing JavaThreadBlockedOnMonitorEnterState in ObjectMonitor::enter_with_contention_mark(), which happens before try_preempt(). The issue can be easily reproduced by adding a delay between JavaThreadBlockedOnMonitorEnterState and the call to try_preempt().
24-12-2024

This problem is reproducible.
24-12-2024

Thank you, Patricio! Yes, there can be a bug somewhere. Will investigate.
20-12-2024

So I can’t see how we can suspend the target vthread while it is mounted. Before the suspend, we called TestTask.ensureBlockedAfterPointA() which makes sure the target vthread is BLOCKED in the synchronized block in A(). Even if somehow blockPermit is true, and the vthread runs again to try acquire the monitor, the attempt would be done while inside the VTMS transition, so no suspend operation con progress. This implies that once the vthread is BLOCKED, any suspend attempt has to caught the vthread as unmounted. It can’t be seen as mounted until it grabs the monitor. So I think there might be an actual bug somewhere.
20-12-2024

It looks like this update is not fully correct for preemptableVirtualThread's. The target virtual thread can be blocked in both unmounted (frequently) and mounted (rarely) state. The mounted state is not expected by the test now but probably should. I see two approaches/choices to fix the test: - for preemptableVirtualThread's expect both JVMTI_ERROR_OPAQUE_FRAME and JVMTI_ERROR_NONE - modify (if possible at all) method ensureBlockedAfterPointA() to wait for unmounted state [~pchilanomate] Could you, please, look at it and share your opinion on this?
19-12-2024

The last time the test was updated by the commit: commit 78b80150e009745b8f28d36c3836f18ad0ca921f Author: Patricio Chilano Mateo <pchilanomate@openjdk.org> Date: Tue Nov 12 15:23:48 2024 +0000 8338383: Implement JEP 491: Synchronize Virtual Threads without Pinning The update was: diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java index d0a2eb65c63..d455a16c998 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest/StopThreadTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -25,23 +25,30 @@ * @test id=default * @summary Verifies JVMTI StopThread support for virtual threads. * @requires vm.continuations + * @library /test/lib * @run main/othervm/native -agentlib:StopThreadTest StopThreadTest */ /* * @test id=no-vmcontinuations * @summary Verifies JVMTI StopThread support for bound virtual threads. - * @run main/othervm/native -agentlib:StopThreadTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StopThreadTest + * @library /test/lib + * @run main/othervm/native -agentlib:StopThreadTest -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations -DboundVThread=true StopThreadTest */ /* * @test id=platform * @summary Verifies JVMTI StopThread support for platform threads. + * @library /test/lib * @run main/othervm/native -agentlib:StopThreadTest StopThreadTest platform */ +import jdk.test.lib.Platform; import java.lang.AssertionError; +import com.sun.management.HotSpotDiagnosticMXBean; +import java.lang.management.ManagementFactory; + /* * The test exercises the JVMTI function: StopThread(jthread). * The test creates a new virtual or platform thread. @@ -53,7 +60,9 @@ */ public class StopThreadTest { private static final String agentLib = "StopThreadTest"; + static final boolean isBoundVThread = Boolean.getBoolean("boundVThread"); static final int JVMTI_ERROR_NONE = 0; + static final int JVMTI_ERROR_OPAQUE_FRAME = 32; static final int THREAD_NOT_SUSPENDED = 13; static final int PASSED = 0; static final int FAILED = 2; @@ -104,7 +113,7 @@ public static void run() { } else { testTaskThread = Thread.ofPlatform().name("TestTaskThread").start(testTask); } - TestTask.ensureAtPointA(); + TestTask.ensureBlockedAfterPointA(testTaskThread); if (is_virtual) { // this check is for virtual target thread only log("\nMain #A.1: unsuspended"); @@ -119,10 +128,12 @@ public static void run() { log("\nMain #A.2: suspended"); suspendThread(testTaskThread); retCode = stopThread(testTaskThread); - if (retCode != JVMTI_ERROR_NONE) { - throwFailed("Main #A.2: expected JVMTI_ERROR_NONE instead of: " + retCode); + int expectedRetCode = preemptableVirtualThread() ? JVMTI_ERROR_OPAQUE_FRAME : JVMTI_ERROR_NONE; + String expectedRetCodeName = preemptableVirtualThread() ? "JVMTI_ERROR_OPAQUE_FRAME" : "JVMTI_ERROR_NONE"; + if (retCode != expectedRetCode) { + throwFailed("Main #A.2: expected " + expectedRetCodeName + " instead of: " + retCode); } else { - log("Main #A.2: got expected JVMTI_ERROR_NONE"); + log("Main #A.2: got expected " + expectedRetCodeName); } resumeThread(testTaskThread); } @@ -168,7 +179,7 @@ public static void run() { static Object lock = new Object(); static void log(String str) { System.out.println(str); } - static volatile boolean atPointA = false; + static volatile boolean reachedPointA = false; static volatile boolean finished = false; static void sleep(long millis) { @@ -179,8 +190,9 @@ static void sleep(long millis) { } } - static void ensureAtPointA() { - while (!atPointA) { + static void ensureBlockedAfterPointA(Thread vt) { + // wait while the thread state is not the expected one + while (!reachedPointA || vt.getState() != Thread.State.BLOCKED) { sleep(1); } } @@ -203,7 +215,7 @@ public void run() { seenExceptionFromA = true; } Thread.interrupted(); - if (!seenExceptionFromA) { + if (!seenExceptionFromA && !preemptableVirtualThread()) { StopThreadTest.setFailed("TestTask.run: expected AssertionError from method A()"); } sleep(1); // to cause yield @@ -241,7 +253,7 @@ public void run() { // - when suspended: JVMTI_ERROR_NONE is expected static void A() { log("TestTask.A: started"); - atPointA = true; + reachedPointA = true; synchronized (lock) { } log("TestTask.A: finished"); @@ -263,4 +275,10 @@ static void C() { log("TestTask.C: finished"); } } + + static boolean preemptableVirtualThread() { + boolean legacyLockingMode = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) + .getVMOption("LockingMode").getValue().equals("1"); + return is_virtual && !isBoundVThread && !legacyLockingMode; + } }
19-12-2024

JVMTI StopThread on a virtual thread is only specified for the case that thread is suspended at an event. An implementation is allowed to send an asynchronous exception to a suspended virtual thread in other cases, these cases are of course implementation specific. It seems that sometimes the test will suspend the virtual thread when it is mounted. The JDK implementation of StopThread will send an async exception when the target virtual thread is suspended while mounted.
08-12-2024

Expected value JVMTI_ERROR_OPAQUE_FRAME was introduced by JDK-8338383 StopThread spec says: JVMTI_ERROR_OPAQUE_FRAME The thread is a suspended virtual thread and the implementation was unable to throw an asynchronous exception from the current frame. So the this value is implementation-specific, JVMTI_ERROR_NONE is valid value as well
05-12-2024