8311218: fatal error: stuck in JvmtiVTMSTransitionDisabler::VTMS_transition_disable
Reviewed-by: lmesnik, alanb
This commit is contained in:
parent
6313223bcd
commit
0f8e4e0a81
@ -223,6 +223,7 @@ JVM_VirtualThreadEnd
|
||||
JVM_VirtualThreadMount
|
||||
JVM_VirtualThreadUnmount
|
||||
JVM_VirtualThreadHideFrames
|
||||
JVM_VirtualThreadDisableSuspend
|
||||
|
||||
# Scoped values
|
||||
JVM_EnsureMaterializedForStackWalk_func
|
||||
|
@ -597,6 +597,7 @@ class methodHandle;
|
||||
do_intrinsic(_notifyJvmtiVThreadMount, java_lang_VirtualThread, notifyJvmtiMount_name, bool_void_signature, F_RN) \
|
||||
do_intrinsic(_notifyJvmtiVThreadUnmount, java_lang_VirtualThread, notifyJvmtiUnmount_name, bool_void_signature, F_RN) \
|
||||
do_intrinsic(_notifyJvmtiVThreadHideFrames, java_lang_VirtualThread, notifyJvmtiHideFrames_name, bool_void_signature, F_RN) \
|
||||
do_intrinsic(_notifyJvmtiVThreadDisableSuspend, java_lang_VirtualThread, notifyJvmtiDisableSuspend_name, bool_void_signature, F_RN) \
|
||||
\
|
||||
/* support for UnsafeConstants */ \
|
||||
do_class(jdk_internal_misc_UnsafeConstants, "jdk/internal/misc/UnsafeConstants") \
|
||||
|
@ -421,6 +421,7 @@ class SerializeClosure;
|
||||
template(notifyJvmtiMount_name, "notifyJvmtiMount") \
|
||||
template(notifyJvmtiUnmount_name, "notifyJvmtiUnmount") \
|
||||
template(notifyJvmtiHideFrames_name, "notifyJvmtiHideFrames") \
|
||||
template(notifyJvmtiDisableSuspend_name, "notifyJvmtiDisableSuspend") \
|
||||
template(doYield_name, "doYield") \
|
||||
template(enter_name, "enter") \
|
||||
template(enterSpecial_name, "enterSpecial") \
|
||||
|
@ -1154,6 +1154,9 @@ JVM_VirtualThreadUnmount(JNIEnv* env, jobject vthread, jboolean hide);
|
||||
JNIEXPORT void JNICALL
|
||||
JVM_VirtualThreadHideFrames(JNIEnv* env, jobject vthread, jboolean hide);
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JVM_VirtualThreadDisableSuspend(JNIEnv* env, jobject vthread, jboolean enter);
|
||||
|
||||
/*
|
||||
* Core reflection support.
|
||||
*/
|
||||
|
@ -220,6 +220,7 @@
|
||||
nonstatic_field(JavaThread, _lock_stack, LockStack) \
|
||||
JVMTI_ONLY(nonstatic_field(JavaThread, _is_in_VTMS_transition, bool)) \
|
||||
JVMTI_ONLY(nonstatic_field(JavaThread, _is_in_tmp_VTMS_transition, bool)) \
|
||||
JVMTI_ONLY(nonstatic_field(JavaThread, _is_disable_suspend, bool)) \
|
||||
\
|
||||
nonstatic_field(LockStack, _top, uint32_t) \
|
||||
\
|
||||
|
@ -822,6 +822,7 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) {
|
||||
case vmIntrinsics::_notifyJvmtiVThreadMount:
|
||||
case vmIntrinsics::_notifyJvmtiVThreadUnmount:
|
||||
case vmIntrinsics::_notifyJvmtiVThreadHideFrames:
|
||||
case vmIntrinsics::_notifyJvmtiVThreadDisableSuspend:
|
||||
#endif
|
||||
break;
|
||||
|
||||
|
@ -493,6 +493,7 @@ bool LibraryCallKit::try_to_inline(int predicate) {
|
||||
case vmIntrinsics::_notifyJvmtiVThreadUnmount: return inline_native_notify_jvmti_funcs(CAST_FROM_FN_PTR(address, OptoRuntime::notify_jvmti_vthread_unmount()),
|
||||
"notifyJvmtiUnmount", false, false);
|
||||
case vmIntrinsics::_notifyJvmtiVThreadHideFrames: return inline_native_notify_jvmti_hide();
|
||||
case vmIntrinsics::_notifyJvmtiVThreadDisableSuspend: return inline_native_notify_jvmti_sync();
|
||||
#endif
|
||||
|
||||
#ifdef JFR_HAVE_INTRINSICS
|
||||
@ -2950,6 +2951,29 @@ bool LibraryCallKit::inline_native_notify_jvmti_hide() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Always update the is_disable_suspend bit.
|
||||
bool LibraryCallKit::inline_native_notify_jvmti_sync() {
|
||||
if (!DoJVMTIVirtualThreadTransitions) {
|
||||
return true;
|
||||
}
|
||||
IdealKit ideal(this);
|
||||
|
||||
{
|
||||
// unconditionally update the is_disable_suspend bit in current JavaThread
|
||||
Node* thread = ideal.thread();
|
||||
Node* arg = _gvn.transform(argument(1)); // argument for notification
|
||||
Node* addr = basic_plus_adr(thread, in_bytes(JavaThread::is_disable_suspend_offset()));
|
||||
const TypePtr *addr_type = _gvn.type(addr)->isa_ptr();
|
||||
|
||||
sync_kit(ideal);
|
||||
access_store_at(nullptr, addr, addr_type, arg, _gvn.type(arg), T_BOOLEAN, IN_NATIVE | MO_UNORDERED);
|
||||
ideal.sync_kit(this);
|
||||
}
|
||||
final_sync(ideal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // INCLUDE_JVMTI
|
||||
|
||||
#ifdef JFR_HAVE_INTRINSICS
|
||||
|
@ -245,6 +245,7 @@ class LibraryCallKit : public GraphKit {
|
||||
#if INCLUDE_JVMTI
|
||||
bool inline_native_notify_jvmti_funcs(address funcAddr, const char* funcName, bool is_start, bool is_end);
|
||||
bool inline_native_notify_jvmti_hide();
|
||||
bool inline_native_notify_jvmti_sync();
|
||||
#endif
|
||||
|
||||
#ifdef JFR_HAVE_INTRINSICS
|
||||
|
@ -4008,6 +4008,22 @@ JVM_ENTRY(void, JVM_VirtualThreadHideFrames(JNIEnv* env, jobject vthread, jboole
|
||||
#endif
|
||||
JVM_END
|
||||
|
||||
// Notification from VirtualThread about disabling JVMTI Suspend in a sync critical section.
|
||||
// Needed to avoid deadlocks with JVMTI suspend mechanism.
|
||||
JVM_ENTRY(void, JVM_VirtualThreadDisableSuspend(JNIEnv* env, jobject vthread, jboolean enter))
|
||||
#if INCLUDE_JVMTI
|
||||
if (!DoJVMTIVirtualThreadTransitions) {
|
||||
assert(!JvmtiExport::can_support_virtual_threads(), "sanity check");
|
||||
return;
|
||||
}
|
||||
assert(thread->is_disable_suspend() != (bool)enter,
|
||||
"nested or unbalanced monitor enter/exit is not allowed");
|
||||
thread->toggle_is_disable_suspend();
|
||||
#else
|
||||
fatal("Should only be called with JVMTI enabled");
|
||||
#endif
|
||||
JVM_END
|
||||
|
||||
/*
|
||||
* Return the current class's class file version. The low order 16 bits of the
|
||||
* returned jint contain the class's major version. The high order 16 bits
|
||||
|
@ -487,6 +487,10 @@ HandshakeOperation* HandshakeState::get_op_for_self(bool allow_suspend, bool che
|
||||
assert(_handshakee == Thread::current(), "Must be called by self");
|
||||
assert(_lock.owned_by_self(), "Lock must be held");
|
||||
assert(allow_suspend || !check_async_exception, "invalid case");
|
||||
if (allow_suspend && _handshakee->is_disable_suspend()) {
|
||||
// filter out suspend operations while JavaThread is in disable_suspend mode
|
||||
allow_suspend = false;
|
||||
}
|
||||
if (!allow_suspend) {
|
||||
return _queue.peek(no_suspend_no_async_exception_filter);
|
||||
} else if (check_async_exception && !_async_exceptions_blocked) {
|
||||
|
@ -440,6 +440,7 @@ JavaThread::JavaThread() :
|
||||
_carrier_thread_suspended(false),
|
||||
_is_in_VTMS_transition(false),
|
||||
_is_in_tmp_VTMS_transition(false),
|
||||
_is_disable_suspend(false),
|
||||
#ifdef ASSERT
|
||||
_is_VTMS_transition_disabler(false),
|
||||
#endif
|
||||
|
@ -317,6 +317,7 @@ class JavaThread: public Thread {
|
||||
volatile bool _carrier_thread_suspended; // Carrier thread is externally suspended
|
||||
bool _is_in_VTMS_transition; // thread is in virtual thread mount state transition
|
||||
bool _is_in_tmp_VTMS_transition; // thread is in temporary virtual thread mount state transition
|
||||
bool _is_disable_suspend; // JVMTI suspend is temporarily disabled; used on current thread only
|
||||
#ifdef ASSERT
|
||||
bool _is_VTMS_transition_disabler; // thread currently disabled VTMS transitions
|
||||
#endif
|
||||
@ -647,6 +648,9 @@ private:
|
||||
void set_is_in_VTMS_transition(bool val);
|
||||
void toggle_is_in_tmp_VTMS_transition() { _is_in_tmp_VTMS_transition = !_is_in_tmp_VTMS_transition; };
|
||||
|
||||
bool is_disable_suspend() const { return _is_disable_suspend; }
|
||||
void toggle_is_disable_suspend() { _is_disable_suspend = !_is_disable_suspend; };
|
||||
|
||||
#ifdef ASSERT
|
||||
bool is_VTMS_transition_disabler() const { return _is_VTMS_transition_disabler; }
|
||||
void set_is_VTMS_transition_disabler(bool val);
|
||||
@ -811,6 +815,7 @@ private:
|
||||
#if INCLUDE_JVMTI
|
||||
static ByteSize is_in_VTMS_transition_offset() { return byte_offset_of(JavaThread, _is_in_VTMS_transition); }
|
||||
static ByteSize is_in_tmp_VTMS_transition_offset() { return byte_offset_of(JavaThread, _is_in_tmp_VTMS_transition); }
|
||||
static ByteSize is_disable_suspend_offset() { return byte_offset_of(JavaThread, _is_disable_suspend); }
|
||||
#endif
|
||||
|
||||
// Returns the jni environment for this thread
|
||||
|
@ -743,12 +743,17 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
}
|
||||
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
|
||||
// unpark carrier thread when pinned
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
try {
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
Thread carrier = carrierThread;
|
||||
if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
|
||||
U.unpark(carrier);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -844,6 +849,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
public void interrupt() {
|
||||
if (Thread.currentThread() != this) {
|
||||
checkAccess();
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
try {
|
||||
synchronized (interruptLock) {
|
||||
interrupted = true;
|
||||
Interruptible b = nioBlocker;
|
||||
@ -855,6 +862,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
Thread carrier = carrierThread;
|
||||
if (carrier != null) carrier.setInterrupt();
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
}
|
||||
} else {
|
||||
interrupted = true;
|
||||
carrierThread.setInterrupt();
|
||||
@ -872,10 +882,15 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
assert Thread.currentThread() == this;
|
||||
boolean oldValue = interrupted;
|
||||
if (oldValue) {
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
try {
|
||||
synchronized (interruptLock) {
|
||||
interrupted = false;
|
||||
carrierThread.clearInterrupt();
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
}
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
@ -899,12 +914,17 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
return Thread.State.RUNNABLE;
|
||||
case RUNNING:
|
||||
// if mounted then return state of carrier thread
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
try {
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
Thread carrierThread = this.carrierThread;
|
||||
if (carrierThread != null) {
|
||||
return carrierThread.threadState();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
}
|
||||
// runnable, mounted
|
||||
return Thread.State.RUNNABLE;
|
||||
case PARKING:
|
||||
@ -1019,6 +1039,8 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
Thread carrier = carrierThread;
|
||||
if (carrier != null) {
|
||||
// include the carrier thread state and name when mounted
|
||||
notifyJvmtiDisableSuspend(true);
|
||||
try {
|
||||
synchronized (carrierThreadAccessLock()) {
|
||||
carrier = carrierThread;
|
||||
if (carrier != null) {
|
||||
@ -1028,6 +1050,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
sb.append(carrier.getName());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
notifyJvmtiDisableSuspend(false);
|
||||
}
|
||||
}
|
||||
// include virtual thread state when not mounted
|
||||
if (carrier == null) {
|
||||
@ -1125,6 +1150,9 @@ final class VirtualThread extends BaseVirtualThread {
|
||||
@JvmtiMountTransition
|
||||
private native void notifyJvmtiHideFrames(boolean hide);
|
||||
|
||||
@IntrinsicCandidate
|
||||
private native void notifyJvmtiDisableSuspend(boolean enter);
|
||||
|
||||
private static native void registerNatives();
|
||||
static {
|
||||
registerNatives();
|
||||
|
@ -37,6 +37,7 @@ static JNINativeMethod methods[] = {
|
||||
{ "notifyJvmtiMount", "(Z)V", (void *)&JVM_VirtualThreadMount },
|
||||
{ "notifyJvmtiUnmount", "(Z)V", (void *)&JVM_VirtualThreadUnmount },
|
||||
{ "notifyJvmtiHideFrames", "(Z)V", (void *)&JVM_VirtualThreadHideFrames },
|
||||
{ "notifyJvmtiDisableSuspend", "(Z)V", (void *)&JVM_VirtualThreadDisableSuspend },
|
||||
};
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 id=default
|
||||
* @summary Do not suspend virtual threads in a critical section.
|
||||
* @bug 8311218
|
||||
* @requires vm.continuations
|
||||
* @library /testlibrary
|
||||
* @run main/othervm SuspendWithInterruptLock
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test id=xint
|
||||
* @summary Do not suspend virtual threads in a critical section.
|
||||
* @bug 8311218
|
||||
* @requires vm.continuations
|
||||
* @library /testlibrary
|
||||
* @run main/othervm -Xint SuspendWithInterruptLock
|
||||
*/
|
||||
|
||||
import jvmti.JVMTIUtils;
|
||||
|
||||
public class SuspendWithInterruptLock {
|
||||
static volatile boolean done;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Thread yielder = Thread.ofVirtual().name("yielder").start(() -> yielder());
|
||||
Thread stateReader = Thread.ofVirtual().name("stateReader").start(() -> stateReader(yielder));
|
||||
Thread suspender = new Thread(() -> suspender(stateReader));
|
||||
suspender.start();
|
||||
|
||||
yielder.join();
|
||||
stateReader.join();
|
||||
suspender.join();
|
||||
}
|
||||
|
||||
static private void yielder() {
|
||||
int iterations = 100_000;
|
||||
while (iterations-- > 0) {
|
||||
Thread.yield();
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
|
||||
static private void stateReader(Thread target) {
|
||||
while (!done) {
|
||||
target.getState();
|
||||
}
|
||||
}
|
||||
|
||||
static private void suspender(Thread target) {
|
||||
while (!done) {
|
||||
suspendThread(target);
|
||||
sleep(1);
|
||||
resumeThread(target);
|
||||
// Allow progress
|
||||
sleep(5);
|
||||
}
|
||||
}
|
||||
|
||||
static void suspendThread(Thread t) {
|
||||
try {
|
||||
JVMTIUtils.suspendThread(t);
|
||||
} catch (JVMTIUtils.JvmtiException e) {
|
||||
if (e.getCode() != JVMTIUtils.JVMTI_ERROR_THREAD_NOT_ALIVE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void resumeThread(Thread t) {
|
||||
try {
|
||||
JVMTIUtils.resumeThread(t);
|
||||
} catch (JVMTIUtils.JvmtiException e) {
|
||||
if (e.getCode() != JVMTIUtils.JVMTI_ERROR_THREAD_NOT_ALIVE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private void sleep(long millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user