#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (open/src/hotspot/share/jfr/support/jfrIntrinsics.cpp:49)
# assert(vthread_epoch == current_epoch) failed: invariant
--------------- T H R E A D ---------------
Current thread (0x00007fea5c31e640): JavaThread "ForkJoinPool-1-worker-323" daemon [_thread_in_Java, id=3696184, stack(0x00007feb193f6000,0x00007feb194f6000) (1024K)]
Stack: [0x00007feb193f6000,0x00007feb194f6000], sp=0x00007feb194f4480, free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0xf6bc4c] JfrIntrinsicSupport::write_checkpoint(JavaThread*)+0x71c (jfrIntrinsics.cpp:49)
v ~RuntimeStub::Shared Runtime jfr_write_checkpoint_blob
J 41250 c2 jdk.internal.event.VirtualThreadStartEvent.commit()V java.base@25-internal (115 bytes)
J 3136 c2 jdk.internal.vm.Continuation.enter(Ljdk/internal/vm/Continuation;Z)V java.base@25-internal (19 bytes)
J 75 jdk.internal.vm.Continuation.enterSpecial(Ljdk/internal/vm/Continuation;ZZ)V java.base@25-internal (0 bytes)
J 1762 c2 jdk.internal.vm.Continuation.run()V java.base@25-internal (586 bytes)
J 4875 c2 java.lang.VirtualThread.runContinuation()V java.base@25-internal (160 bytes)
J 8217 c2 java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Ljava/util/concurrent/ForkJoinTask;I)V java.base@25-internal (18 bytes)
J 7901 c2 java.util.concurrent.ForkJoinPool.runWorker(Ljava/util/concurrent/ForkJoinPool$WorkQueue;)V java.base@25-internal (431 bytes)
j java.util.concurrent.ForkJoinWorkerThread.run()V+31 java.base@25-internal
v ~StubRoutines::call_stub
V [libjvm.so+0xef366c] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x4ac (javaCalls.cpp:415)
V [libjvm.so+0xef3d53] JavaCalls::call_virtual(JavaValue*, Klass*, Symbol*, Symbol*, JavaCallArguments*, JavaThread*)+0x2f3 (javaCalls.cpp:323)
V [libjvm.so+0xef4356] JavaCalls::call_virtual(JavaValue*, Handle, Klass*, Symbol*, Symbol*, JavaThread*)+0x76 (javaCalls.cpp:185)
V [libjvm.so+0x10696b3] thread_entry(JavaThread*, JavaThread*)+0x93 (jvm.cpp:2788)
V [libjvm.so+0xf2ddbe] JavaThread::thread_main_inner()+0xee (javaThread.cpp:776)
V [libjvm.so+0x1882e06] Thread::call_run()+0xb6 (thread.cpp:231)
V [libjvm.so+0x155b068] thread_native_entry(Thread*)+0x128 (os_linux.cpp:877)
// open/src/hotspot/share/jfr/support/jfrIntrinsics.cpp
#ifdef ASSERT
static void assert_precondition(JavaThread* jt) {
assert(jt != nullptr, "invariant");
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_java(jt);)
assert(jt->has_last_Java_frame(), "invariant");
}
static void assert_epoch_identity(JavaThread* jt, u2 current_epoch) {
assert_precondition(jt);
// Verify the epoch updates got written through also to the vthread object.
const u2 epoch_raw = ThreadIdAccess::epoch(jt->vthread()); // loading the vhread epoch from the oop, not the jfr_thread_local
const bool excluded = epoch_raw & excluded_bit;
assert(!excluded, "invariant");
assert(!JfrThreadLocal::is_excluded(jt), "invariant");
const u2 vthread_epoch = epoch_raw & epoch_mask;
assert(vthread_epoch == current_epoch, "invariant"); // <<--
}
#endif // ASSERT
void* JfrIntrinsicSupport::write_checkpoint(JavaThread* jt) {
DEBUG_ONLY(assert_precondition(jt);)
assert(JfrThreadLocal::is_vthread(jt), "invariant");
const u2 vthread_thread_local_epoch = JfrThreadLocal::vthread_epoch(jt); // loading the vthread epoch from the jfr_thread_local, not the vthread oop
const u2 current_epoch = ThreadIdAccess::current_epoch();
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, jt));
if (vthread_thread_local_epoch == current_epoch) {
// After the epoch test in the intrinsic, the thread sampler interleaved
// and suspended the thread. As part of taking a sample, it updated
// the vthread object and the thread local "for us". We are good.
DEBUG_ONLY(assert_epoch_identity(jt, current_epoch);) // <<---
ThreadInVMfromJava transition(jt);
return JfrJavaEventWriter::event_writer(jt);
}
const traceid vthread_tid = JfrThreadLocal::vthread_id(jt);
// Transition before reading the epoch generation anew, now as _thread_in_vm. Can safepoint here.
ThreadInVMfromJava transition(jt);
JfrThreadLocal::set_vthread_epoch(jt, vthread_tid, ThreadIdAccess::current_epoch());
return JfrJavaEventWriter::event_writer(jt);
}
// open/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
// called from JVM_SetCurrentThread()
void JfrThreadLocal::on_set_current_thread(JavaThread* jt, oop thread) {
assert(jt != nullptr, "invariant");
assert(thread != nullptr, "invariant");
JfrThreadLocal* const tl = jt->jfr_thread_local();
if (!is_virtual(jt, thread)) {
Atomic::release_store(&tl->_vthread, false);
return;
}
Atomic::store(&tl->_vthread_id, AccessThreadTraceId::id(thread));
const u2 epoch_raw = AccessThreadTraceId::epoch(thread);
const bool excluded = epoch_raw & excluded_bit;
Atomic::store(&tl->_vthread_excluded, excluded);
if (!excluded) {
Atomic::store(&tl->_vthread_epoch, static_cast<u2>(epoch_raw & epoch_mask)); // the vthread epoch is only stored into the jfr_thread_local for threads that are not excluded.
}
Atomic::release_store(&tl->_vthread, true);
}
Our JFR APIs allows for a virtual thread to be included, also after it has been mounted (i.e. notified through JfrThreadLocal::on_set_current_thread()).
The problem is we can end up with a state where the jfr_thread_local reflects the epoch of the last included virtual thread that was mounted, because the epoch is not updated when an excluded virtual thread becomes included.
We should update the epoch in the jfr_thread_local when a virtual thread becomes included. It is sufficient to set a sentinel that always is unequal to the current epoch, to force metadata to be written on first use - we have such a sentinel, its 0.
diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
index e1f72606c50..0a14e23b87c 100644
--- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
+++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
@@ -298,7 +298,9 @@ void JfrThreadLocal::exclude_vthread(const JavaThread* jt) {
}
void JfrThreadLocal::include_vthread(const JavaThread* jt) {
- set(&jt->jfr_thread_local()->_vthread_excluded, false);
+ JfrThreadLocal* const tl = jt->jfr_thread_local();
+ Atomic::store(&tl->_vthread_epoch, static_cast<u2>(0));
+ set(&tl->_vthread_excluded, false);
JfrJavaEventWriter::include(vthread_id(jt), jt);
}
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (open/src/hotspot/share/jfr/support/jfrIntrinsics.cpp:49)
# assert(vthread_epoch == current_epoch) failed: invariant
--------------- T H R E A D ---------------
Current thread (0x00007fea5c31e640): JavaThread "ForkJoinPool-1-worker-323" daemon [_thread_in_Java, id=3696184, stack(0x00007feb193f6000,0x00007feb194f6000) (1024K)]
Stack: [0x00007feb193f6000,0x00007feb194f6000], sp=0x00007feb194f4480, free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0xf6bc4c] JfrIntrinsicSupport::write_checkpoint(JavaThread*)+0x71c (jfrIntrinsics.cpp:49)
v ~RuntimeStub::Shared Runtime jfr_write_checkpoint_blob
J 41250 c2 jdk.internal.event.VirtualThreadStartEvent.commit()V java.base@25-internal (115 bytes)
J 3136 c2 jdk.internal.vm.Continuation.enter(Ljdk/internal/vm/Continuation;Z)V java.base@25-internal (19 bytes)
J 75 jdk.internal.vm.Continuation.enterSpecial(Ljdk/internal/vm/Continuation;ZZ)V java.base@25-internal (0 bytes)
J 1762 c2 jdk.internal.vm.Continuation.run()V java.base@25-internal (586 bytes)
J 4875 c2 java.lang.VirtualThread.runContinuation()V java.base@25-internal (160 bytes)
J 8217 c2 java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Ljava/util/concurrent/ForkJoinTask;I)V java.base@25-internal (18 bytes)
J 7901 c2 java.util.concurrent.ForkJoinPool.runWorker(Ljava/util/concurrent/ForkJoinPool$WorkQueue;)V java.base@25-internal (431 bytes)
j java.util.concurrent.ForkJoinWorkerThread.run()V+31 java.base@25-internal
v ~StubRoutines::call_stub
V [libjvm.so+0xef366c] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x4ac (javaCalls.cpp:415)
V [libjvm.so+0xef3d53] JavaCalls::call_virtual(JavaValue*, Klass*, Symbol*, Symbol*, JavaCallArguments*, JavaThread*)+0x2f3 (javaCalls.cpp:323)
V [libjvm.so+0xef4356] JavaCalls::call_virtual(JavaValue*, Handle, Klass*, Symbol*, Symbol*, JavaThread*)+0x76 (javaCalls.cpp:185)
V [libjvm.so+0x10696b3] thread_entry(JavaThread*, JavaThread*)+0x93 (jvm.cpp:2788)
V [libjvm.so+0xf2ddbe] JavaThread::thread_main_inner()+0xee (javaThread.cpp:776)
V [libjvm.so+0x1882e06] Thread::call_run()+0xb6 (thread.cpp:231)
V [libjvm.so+0x155b068] thread_native_entry(Thread*)+0x128 (os_linux.cpp:877)
// open/src/hotspot/share/jfr/support/jfrIntrinsics.cpp
#ifdef ASSERT
static void assert_precondition(JavaThread* jt) {
assert(jt != nullptr, "invariant");
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_java(jt);)
assert(jt->has_last_Java_frame(), "invariant");
}
static void assert_epoch_identity(JavaThread* jt, u2 current_epoch) {
assert_precondition(jt);
// Verify the epoch updates got written through also to the vthread object.
const u2 epoch_raw = ThreadIdAccess::epoch(jt->vthread()); // loading the vhread epoch from the oop, not the jfr_thread_local
const bool excluded = epoch_raw & excluded_bit;
assert(!excluded, "invariant");
assert(!JfrThreadLocal::is_excluded(jt), "invariant");
const u2 vthread_epoch = epoch_raw & epoch_mask;
assert(vthread_epoch == current_epoch, "invariant"); // <<--
}
#endif // ASSERT
void* JfrIntrinsicSupport::write_checkpoint(JavaThread* jt) {
DEBUG_ONLY(assert_precondition(jt);)
assert(JfrThreadLocal::is_vthread(jt), "invariant");
const u2 vthread_thread_local_epoch = JfrThreadLocal::vthread_epoch(jt); // loading the vthread epoch from the jfr_thread_local, not the vthread oop
const u2 current_epoch = ThreadIdAccess::current_epoch();
MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, jt));
if (vthread_thread_local_epoch == current_epoch) {
// After the epoch test in the intrinsic, the thread sampler interleaved
// and suspended the thread. As part of taking a sample, it updated
// the vthread object and the thread local "for us". We are good.
DEBUG_ONLY(assert_epoch_identity(jt, current_epoch);) // <<---
ThreadInVMfromJava transition(jt);
return JfrJavaEventWriter::event_writer(jt);
}
const traceid vthread_tid = JfrThreadLocal::vthread_id(jt);
// Transition before reading the epoch generation anew, now as _thread_in_vm. Can safepoint here.
ThreadInVMfromJava transition(jt);
JfrThreadLocal::set_vthread_epoch(jt, vthread_tid, ThreadIdAccess::current_epoch());
return JfrJavaEventWriter::event_writer(jt);
}
// open/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
// called from JVM_SetCurrentThread()
void JfrThreadLocal::on_set_current_thread(JavaThread* jt, oop thread) {
assert(jt != nullptr, "invariant");
assert(thread != nullptr, "invariant");
JfrThreadLocal* const tl = jt->jfr_thread_local();
if (!is_virtual(jt, thread)) {
Atomic::release_store(&tl->_vthread, false);
return;
}
Atomic::store(&tl->_vthread_id, AccessThreadTraceId::id(thread));
const u2 epoch_raw = AccessThreadTraceId::epoch(thread);
const bool excluded = epoch_raw & excluded_bit;
Atomic::store(&tl->_vthread_excluded, excluded);
if (!excluded) {
Atomic::store(&tl->_vthread_epoch, static_cast<u2>(epoch_raw & epoch_mask)); // the vthread epoch is only stored into the jfr_thread_local for threads that are not excluded.
}
Atomic::release_store(&tl->_vthread, true);
}
Our JFR APIs allows for a virtual thread to be included, also after it has been mounted (i.e. notified through JfrThreadLocal::on_set_current_thread()).
The problem is we can end up with a state where the jfr_thread_local reflects the epoch of the last included virtual thread that was mounted, because the epoch is not updated when an excluded virtual thread becomes included.
We should update the epoch in the jfr_thread_local when a virtual thread becomes included. It is sufficient to set a sentinel that always is unequal to the current epoch, to force metadata to be written on first use - we have such a sentinel, its 0.
diff --git a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
index e1f72606c50..0a14e23b87c 100644
--- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
+++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp
@@ -298,7 +298,9 @@ void JfrThreadLocal::exclude_vthread(const JavaThread* jt) {
}
void JfrThreadLocal::include_vthread(const JavaThread* jt) {
- set(&jt->jfr_thread_local()->_vthread_excluded, false);
+ JfrThreadLocal* const tl = jt->jfr_thread_local();
+ Atomic::store(&tl->_vthread_epoch, static_cast<u2>(0));
+ set(&tl->_vthread_excluded, false);
JfrJavaEventWriter::include(vthread_id(jt), jt);
}
- links to
-
Commit(master) openjdk/jdk/e5666f56
-
Review(master) openjdk/jdk/24041