On Fri, 17 Nov 2023 20:29:11 GMT, Serguei Spitsyn <sspit...@openjdk.org> wrote:

>> This is a fix of a performance/scalability related issue. The 
>> `JvmtiThreadState` objects for virtual thread filtered events enabled 
>> globally are created eagerly because it is needed when the 
>> `interp_only_mode` is enabled. Otherwise, some events which are generated in 
>> `interp_only_mode` from the debugging version of interpreter chunks can be 
>> missed.
>> However, it has to be okay to avoid eager creation of these object if no 
>> `interp_only_mode` has ever been requested.
>> It seems to be an extremely important optimization to create 
>> JvmtiThreadState objects lazily in such cases.
>> It is done by introducing the flag 
>> `JvmtiThreadState::_seen_interp_only_mode` which indicates when the 
>> `JvmtiThreadState` objects have to be created eagerly.
>> 
>> Additionally, the fix includes the following related changes:
>>  - Use condition double checking idiom for `MutexLocker 
>> mu(JvmtiThreadState_lock)` in the function 
>> `JvmtiVTMSTransitionDisabler::VTMS_mount_end` which is on a 
>> performance-critical path and looks like this:
>> 
>>   JvmtiThreadState* state = thread->jvmti_thread_state();
>>   if (state != nullptr && state->is_pending_interp_only_mode()) {
>>     MutexLocker mu(JvmtiThreadState_lock);
>>     state = thread->jvmti_thread_state();
>>     if (state != nullptr && state->is_pending_interp_only_mode()) {
>>       JvmtiEventController::enter_interp_only_mode();
>>     }
>>   }
>> 
>> 
>>  - Add extra check of `JvmtiExport::can_support_virtual_threads()` when 
>> virtual thread mount and unmount are posted.
>>  - Minor: Added a `ThreadsListHandle` to the 
>> `JvmtiEventControllerPrivate::enter_interp_only_mode`. It is needed because 
>> of the dynamic creation of compensating carrier threads which is racy for 
>> JVMTI `SetEventNotificationMode` implementation.
>> 
>> Performance mesurements:
>> - Without this fix the test provided by the bug submitter gives execution 
>> numbers:
>>   - no ClassLoad events enabled:     3251 ms
>>   - ClassLoad events are enabled: 40534 ms
>>   
>> - With the fix:
>>   - no ClassLoad events enabled:    3270 ms
>>   - ClassLoad events are enabled:   3385 ms
>>   
>> Testing:
>>  - Ran mach5 tiers 1-6, no regressions are noticed
>
> Serguei Spitsyn has updated the pull request incrementally with one 
> additional commit since the last revision:
> 
>   review: add comment for new ThreadsListHandle use

I'm going to resurrect the failing guarantee() code and part of the stack trace 
that was removed
and yack a bit about this code path.

Here's the location of the failing guarantee():

void Handshake::execute(HandshakeClosure* hs_cl, ThreadsListHandle* tlh, 
JavaThread* target) {
  . . .
  guarantee(target != nullptr, "must be");
  if (tlh == nullptr) {
    guarantee(Thread::is_JavaThread_protected_by_TLH(target),
              "missing ThreadsListHandle in calling context.");


and here's part of the stack trace that got us here:

V  [libjvm.so+0x117937d]  
JvmtiEventControllerPrivate::enter_interp_only_mode(JvmtiThreadState*)+0x45d  
(jvmtiEventController.cpp:402)
V  [libjvm.so+0x1179520]  
JvmtiEventControllerPrivate::recompute_thread_enabled(JvmtiThreadState*) [clone 
.part.0]+0x190  (jvmtiEventController.cpp:632)
V  [libjvm.so+0x117a1e1]  
JvmtiEventControllerPrivate::thread_started(JavaThread*)+0x351  
(jvmtiEventController.cpp:1174)
V  [libjvm.so+0x117e608]  JvmtiExport::get_jvmti_thread_state(JavaThread*)+0x98 
 (jvmtiExport.cpp:424)
V  [libjvm.so+0x118a86c]  JvmtiExport::post_field_access(JavaThread*, Method*, 
unsigned char*, Klass*, Handle, _jfieldID*)+0x6c  (jvmtiExport.cpp:2214)


This must have been a stack trace from a build with some optimizations enabled 
because
when I look at last night's code base, I see 8 frames from the 
JvmtiExport::get_jvmti_thread_state()
call to Handshake::execute() with three params:


src/hotspot/share/prims/jvmtiExport.cpp:
    JvmtiExport::get_jvmti_thread_state(JavaThread *thread) {
      assert(thread == JavaThread::current(), "must be current thread");
      if (thread->is_vthread_mounted() && thread->jvmti_thread_state() == 
nullptr) {
        JvmtiEventController::thread_started(thread);
      }

The above code asserts that the `thread` parameter is the current thread so
we know that the calling thread is operating on itself.


src/hotspot/share/prims/jvmtiEventController.cpp
    JvmtiEventControllerPrivate::thread_started(JavaThread *thread) {
      assert(thread == Thread::current(), "must be current thread");
<snip>
      // if we have any thread filtered events globally enabled, create/update 
the thread state
      if (is_any_thread_filtered_event_enabled_globally()) { // intentionally 
racy
        JvmtiThreadState::state_for(thread);

The above code asserts that the `thread` parameter is the current thread so
we know that the calling thread is operating on itself. Note that we're calling
the single parameter version of `JvmtiThreadState::state_for()` here and in
that case the `thread_handle` parameter is `Handle thread_handle = Handle()`.


src/hotspot/share/prims/jvmtiThreadState.inline.hpp
    inline JvmtiThreadState* JvmtiThreadState::state_for(JavaThread *thread, 
Handle thread_handle) {
      // In a case of unmounted virtual thread the thread can be null.
      JvmtiThreadState* state = thread_handle == nullptr ? 
thread->jvmti_thread_state() :
                                                    
java_lang_Thread::jvmti_thread_state(thread_handle());
      if (state == nullptr) {
        MutexLocker mu(JvmtiThreadState_lock);
        // check again with the lock held
        state = state_for_while_locked(thread, thread_handle());
        JvmtiEventController::recompute_thread_filtered(state);

The above code grabs the JvmtiThreadState from the `thread` parameter and
passes that to the `JvmtiEventController::recompute_thread_filtered()` call.
We know that `thread` parameter is the current thread.


src/hotspot/share/prims/jvmtiEventController.cpp
    void
    JvmtiEventControllerPrivate::recompute_thread_filtered(JvmtiThreadState 
*state) {
<snip>
      if (is_any_thread_filtered_event_enabled_globally()) {
        JvmtiEventControllerPrivate::recompute_thread_enabled(state);

The above code is just a filter wrapper for calling 
`JvmtiEventControllerPrivate::recompute_thread_enabled(`.


src/hotspot/share/prims/jvmtiEventController.cpp
    jlong
    JvmtiEventControllerPrivate::recompute_thread_enabled(JvmtiThreadState 
*state) {
<snip>
      // compute interp_only mode
      bool should_be_interp = (any_env_enabled & INTERP_EVENT_BITS) != 0 || 
has_frame_pops;
      bool is_now_interp = state->is_interp_only_mode();

      if (should_be_interp != is_now_interp) {
        if (should_be_interp) {
          enter_interp_only_mode(state);

The above code determines that the current thread needs to be in
interpreted mode so it calls `enter_interp_only_mode(state)`.


src/hotspot/share/prims/jvmtiEventController.cpp
    void JvmtiEventControllerPrivate::enter_interp_only_mode(JvmtiThreadState 
*state) {
<snip>
      JavaThread *target = state->get_thread();
      Thread *current = Thread::current();
<snip>
      if (target->is_handshake_safe_for(current)) {
        hs.do_thread(target);
      } else {
        assert(state->get_thread() != nullptr, "sanity check");
        Handshake::execute(&hs, target);

We know from our previous code analysis that the `JvmtiThreadState *state`
we were passed was fetched from the current thread. See 
`JvmtiThreadState::state_for`
above. So that `target` thread and the `current` should be the same thread.

Why does this check return false:


    if (target->is_handshake_safe_for(current)) {


which allows us to travel down this call: `Handshake::execute(&hs, target)`


src/hotspot/share/runtime/handshake.cpp
    void Handshake::execute(HandshakeClosure* hs_cl, JavaThread* target) {
      // tlh == nullptr means we rely on a ThreadsListHandle somewhere
      // in the caller's context (and we sanity check for that).
      Handshake::execute(hs_cl, nullptr, target);
    }

The two parameter version of `Handshake::execute()` is just a wrapper
that passed a nullptr for the ThreadsListHandle to the three parameter
version of `Handshake::execute()`. And that's how we get to the failing
guarantee()...


src/hotspot/share/runtime/handshake.cpp
    void Handshake::execute(HandshakeClosure* hs_cl, ThreadsListHandle* tlh, 
JavaThread* target) {
      JavaThread* self = JavaThread::current();
      HandshakeOperation op(hs_cl, target, Thread::current());

      jlong start_time_ns = os::javaTimeNanos();

      guarantee(target != nullptr, "must be");
      if (tlh == nullptr) {
        guarantee(Thread::is_JavaThread_protected_by_TLH(target),
                  "missing ThreadsListHandle in calling context.");
        target->handshake_state()->add_operation(&op);


One possible fix for the guarantee is this version:

        guarantee(self == target || 
Thread::is_JavaThread_protected_by_TLH(target),
                  "missing ThreadsListHandle in calling context.");


However, that ignores why this code in 
JvmtiEventControllerPrivate::enter_interp_only_mode
returned false:


    if (target->is_handshake_safe_for(current)) {


when we have these local variable values:

      JavaThread *target = state->get_thread();
      Thread *current = Thread::current();



src/hotspot/share/runtime/javaThread.hpp
      // A JavaThread can always safely operate on it self and other threads
      // can do it safely if they are the active handshaker.
      bool is_handshake_safe_for(Thread* th) const {
        return _handshake.active_handshaker() == th || this == th;
      }

It seems to me that this portion of the logic: `this == th` should have
returned `true` and not `false`. What am I missing here?

-------------

PR Comment: https://git.openjdk.org/jdk/pull/16686#issuecomment-1832699046

Reply via email to