diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index 0b7e64a3ba6..26d865057e6 100644 --- a/src/hotspot/share/runtime/continuation.cpp +++ b/src/hotspot/share/runtime/continuation.cpp @@ -87,7 +87,8 @@ class UnmountBeginMark : public StackObj { } ~UnmountBeginMark() { assert(!_current->is_suspended() - JVMTI_ONLY(|| (_current->is_vthread_transition_disabler() && _result != freeze_ok)), "must be"); + JVMTI_ONLY(|| (_result != freeze_ok && + (_current->is_vthread_transition_disabler() || _current->is_disable_suspend()))), "must be"); assert(_current->is_in_vthread_transition(), "must be"); if (_result != freeze_ok) { diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 08d3cde8562..9e3c629ad78 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -873,7 +873,7 @@ public: // Atomic version; invoked by a thread other than the owning thread. bool in_critical_atomic() { return AtomicAccess::load(&_jni_active_critical) > 0; } - bool jni_deferred_suspension() { return AtomicAccess::load(&_jni_deferred_suspension_count); } + bool jni_deferred_suspension() const { return AtomicAccess::load(&_jni_deferred_suspension_count); } inline void enter_jni_deferred_suspension(); void exit_jni_deferred_suspension() { precond(Thread::current() == this); diff --git a/src/hotspot/share/runtime/mountUnmountDisabler.cpp b/src/hotspot/share/runtime/mountUnmountDisabler.cpp index 65a82d6c563..277a841a88c 100644 --- a/src/hotspot/share/runtime/mountUnmountDisabler.cpp +++ b/src/hotspot/share/runtime/mountUnmountDisabler.cpp @@ -129,7 +129,7 @@ bool MountUnmountDisabler::is_start_transition_disabled(JavaThread* thread, oop int base_disable_count = notify_jvmti_events() ? 1 : 0; return java_lang_Thread::vthread_transition_disable_count(vthread) > 0 || global_vthread_transition_disable_count() > base_disable_count - JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() && + JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() && !thread->is_disable_suspend() && (JvmtiVTSuspender::is_vthread_suspended(java_lang_Thread::thread_id(vthread)) || thread->is_suspended()))); } diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java new file mode 100644 index 00000000000..db2eff4c1b5 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java @@ -0,0 +1,109 @@ +/* + * 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 8376621 + * @summary Suspend virtual thread while it's inside disableSuspendAndPreempt region + * @requires vm.continuations + * @requires vm.jvmti + * @library /test/lib /test/hotspot/jtreg/testlibrary + * @run main/othervm SuspendResume4 + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.io.File; + +import jvmti.JVMTIUtils; + +public class SuspendResume4 { + native static void suspendThread(Thread thread); + native static void resumeThread(Thread thread); + + public static void main(String[] args) throws Exception { + // Run test in child VM where Locale won't be initialized already by jtreg + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, + "-agentpath:" + Utils.TEST_NATIVE_PATH + File.separator + System.mapLibraryName("SuspendResume4"), + "SuspendResume4$Test"); + OutputAnalyzer output = ProcessTools.executeProcess(pb); + System.out.println(output.getStdout()); + output.shouldHaveExitValue(0); + } + + static class Test{ + static String targetName; + + private void runTest() throws Exception { + // start target vthread + Thread target = Thread.ofVirtual().name("target").start(() -> { + // Give time for reader to get suspended in + // disableSuspendAndPreempt region. + spinWaitMillis(100); + // Force unmounting. If reader was suspended inside + // disableSuspendAndPreempt region this will block + // in VirtualThread.unmount. + Thread.yield(); + }); + + // start clinit contender + Thread contender = Thread.ofPlatform().name("contender").start(() -> { + "JAVA".toLowerCase(java.util.Locale.ROOT); + }); + + // start vthread that reads target's state + Thread reader = Thread.ofVirtual().name("reader").start(() -> { + targetName = "name: " + target; + }); + + // start suspend/resumer + Thread suspender = Thread.ofPlatform().name("suspender").start(() -> { + SuspendResume4.suspendThread(reader); + // Give target time for Thread.yield + spinWaitMillis(100); + SuspendResume4.resumeThread(reader); + }); + + target.join(); + contender.join(); + suspender.join(); + reader.join(); + } + + public static void main(String[] args) throws Exception { + Test obj = new Test(); + obj.runTest(); + } + + static void spinWaitMillis(long millis) { + long durationNanos = millis * 1_000_000L; + long start = System.nanoTime(); + while (System.nanoTime() - start < durationNanos) { + Thread.onSpinWait(); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp new file mode 100644 index 00000000000..38e90889b7a --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include "jvmti_common.hpp" + +// set by Agent_OnLoad +static jvmtiEnv* jvmti = nullptr; + +extern "C" { + +JNIEXPORT void JNICALL +Java_SuspendResume4_suspendThread(JNIEnv* jni, jclass klass, jthread thread) { + jvmtiError err = jvmti->SuspendThread(thread); + if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_THREAD_NOT_ALIVE) { + jni->FatalError("error in JVMTI SuspendThread"); + } +} + +JNIEXPORT void JNICALL +Java_SuspendResume4_resumeThread(JNIEnv* jni, jclass klass, jthread thread) { + jvmtiError err = jvmti->ResumeThread(thread); + if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_THREAD_NOT_ALIVE) { + jni->FatalError("error in JVMTI ResumeThread"); + } +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { + jvmtiCapabilities caps; + jvmtiError err; + + printf("Agent_OnLoad: started\n"); + if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) { + LOG("Agent_OnLoad: error in GetEnv"); + return JNI_ERR; + } + + memset(&caps, 0, sizeof(caps)); + caps.can_suspend = 1; + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("Agent_OnLoad: error in JVMTI AddCapabilities: %d\n", err); + } + + printf("Agent_OnLoad: finished\n"); + + return 0; +} + +} // extern "C"