On Thu, 17 Oct 2024 14:28:30 GMT, Patricio Chilano Mateo 
<pchilanom...@openjdk.org> wrote:

> This is the implementation of JEP 491: Synchronize Virtual Threads without 
> Pinning. See [JEP 491](https://bugs.openjdk.org/browse/JDK-8337395) for 
> further details.
> 
> In order to make the code review easier the changes have been split into the 
> following initial 4 commits:
> 
> - Changes to allow unmounting a virtual thread that is currently holding 
> monitors.
> - Changes to allow unmounting a virtual thread blocked on synchronized trying 
> to acquire the monitor.
> - Changes to allow unmounting a virtual thread blocked in `Object.wait()` and 
> its timed-wait variants.
> - Changes to tests, JFR pinned event, and other changes in the JDK libraries.
> 
> The changes fix pinning issues for all 4 ports that currently implement 
> continuations: x64, aarch64, riscv and ppc. Note: ppc changes were added 
> recently and stand in its own commit after the initial ones.
> 
> The changes fix pinning issues when using `LM_LIGHTWEIGHT`, i.e. the default 
> locking mode, (and `LM_MONITOR` which comes for free), but not when using 
> `LM_LEGACY` mode. Note that the `LockingMode` flag has already been 
> deprecated ([JDK-8334299](https://bugs.openjdk.org/browse/JDK-8334299)), with 
> the intention to remove `LM_LEGACY` code in future releases.
> 
> 
> ## Summary of changes
> 
> ### Unmount virtual thread while holding monitors
> 
> As stated in the JEP, currently when a virtual thread enters a synchronized 
> method or block, the JVM records the virtual thread's carrier platform thread 
> as holding the monitor, not the virtual thread itself. This prevents the 
> virtual thread from being unmounted from its carrier, as ownership 
> information would otherwise go wrong. In order to fix this limitation we will 
> do two things:
> 
> - We copy the oops stored in the LockStack of the carrier to the stackChunk 
> when freezing (and clear the LockStack). We copy the oops back to the 
> LockStack of the next carrier when thawing for the first time (and clear them 
> from the stackChunk). Note that we currently assume carriers don't hold 
> monitors while mounting virtual threads.
> 
> - For inflated monitors we now record the `java.lang.Thread.tid` of the owner 
> in the ObjectMonitor's `_owner` field instead of a JavaThread*. This allows 
> us to tie the owner of the monitor to a `java.lang.Thread` instance, rather 
> than to a JavaThread which is only created per platform thread. The tid is 
> already a 64 bit field so we can ignore issues of the counter wrapping around.
> 
> #### General notes about this part:
> 
> - Since virtual threads don't need to worry about holding monitors anymo...

src/hotspot/share/prims/jvmtiEnvBase.cpp line 1082:

> 1080:     } else {
> 1081:       assert(vthread != nullptr, "no vthread oop");
> 1082:       oop oopCont = java_lang_VirtualThread::continuation(vthread);

Nit: The name `oopCont` does not match the HotSpot naming convention.
       What about `cont_oop` or even better just `cont` as at the line 2550?

src/hotspot/share/prims/jvmtiExport.cpp line 1682:

> 1680: 
> 1681:   // On preemption JVMTI state rebinding has already happened so get it 
> always directly from the oop.
> 1682:   JvmtiThreadState *state = 
> java_lang_Thread::jvmti_thread_state(JNIHandles::resolve(vthread));

I'm not sure this change is right. The `get_jvmti_thread_state()` has a role to 
lazily create a `JvmtiThreadState` if it was not created before. With this 
change the `JvmtiThreadState` creation can be missed if the `unmount` event is 
the first event encountered for this particular virtual thread. You probably 
remember that lazy creation of  the `JvmtiThreadState`'s is an important 
optimization to avoid big performance overhead when a JVMTI agent is present.

src/hotspot/share/prims/jvmtiExport.cpp line 2879:

> 2877:   
> JvmtiVTMSTransitionDisabler::start_VTMS_transition((jthread)vthread.raw_value(),
>  /* is_mount */ true);
> 2878:   current->rebind_to_jvmti_thread_state_of(current->threadObj());
> 2879: }

This function looks a little bit unusual.
I need to think about the consequences but do not see anything bad so far.
I'll look at the `ObjectMonitor` and `continuation` side updates to get more 
details on this.

src/hotspot/share/runtime/continuation.cpp line 88:

> 86:       if (_target->has_async_exception_condition()) {
> 87:         _failed = true;
> 88:       }

Q: I wonder why the failed conditions are not checked before the 
`start_VTMS_transition()` call. At least, it'd be nice to add a comment about 
on this.

src/hotspot/share/runtime/continuation.cpp line 115:

> 113:       if (jvmti_present) {
> 114:         _target->rebind_to_jvmti_thread_state_of(_target->threadObj());
> 115:         if (JvmtiExport::should_post_vthread_mount()) {

This has to be `JvmtiExport::should_post_vthread_unmount()` instead of 
`JvmtiExport::should_post_vthread_mount()`.
Also, it'd be nice to add a comment explaining why the event posting is 
postponed to the `unmount` end point.

src/hotspot/share/runtime/continuation.cpp line 134:

> 132:   return true;
> 133: }
> 134: #endif // INCLUDE_JVMTI

Could you, please, consider the simplification below?


#if INCLUDE_JVMTI
// return true if started vthread unmount
bool jvmti_unmount_begin(JavaThread* target) {
  assert(!target->is_in_any_VTMS_transition(), "must be");

  // Don't preempt if there is a pending popframe or earlyret operation. This 
can
  // be installed in start_VTMS_transition() so we need to check it here.
  if (JvmtiExport::can_pop_frame() || JvmtiExport::can_force_early_return()) {
    JvmtiThreadState* state = target->jvmti_thread_state();
    if (target->has_pending_popframe() || (state != nullptr && 
state->is_earlyret_pending())) {
      return false;
    }
  }
  // Don't preempt in case there is an async exception installed since
  // we would incorrectly throw it during the unmount logic in the carrier.
  if (target->has_async_exception_condition()) {
    return false;
  }
  if (JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) {
    JvmtiVTMSTransitionDisabler::VTMS_vthread_unmount(target->vthread(), true);
  } else {
    target->set_is_in_VTMS_transition(true);
    // not need to call: 
java_lang_Thread::set_is_in_VTMS_transition(target->vthread(), true)
  }
  return false;
}

static bool is_vthread_safe_to_preempt_for_jvmti(JavaThread* target) {
  if (target->is_in_VTMS_transition()) {
    // We caught target at the end of a mount transition.
    return false;
  }
  return true;
}
#endif // INCLUDE_JVMTI
...
static bool is_vthread_safe_to_preempt(JavaThread* target, oop vthread) {
  assert(java_lang_VirtualThread::is_instance(vthread), "");
  if (java_lang_VirtualThread::state(vthread) != 
java_lang_VirtualThread::RUNNING) {  // inside transition
    return false;
  }
  return JVMTI_ONLY(is_vthread_safe_to_preempt_for_jvmti(target)) 
NOT_JVMTI(true);
}
...
int Continuation::try_preempt(JavaThread* target, oop continuation) {
  verify_preempt_preconditions(target, continuation);

  if (LockingMode == LM_LEGACY) {
    return freeze_unsupported;
  }
  if (!is_safe_vthread_to_preempt(target, target->vthread())) {
    return freeze_pinned_native;
  }
  JVMTI_ONLY(if (!jvmti_unmount_begin(target)) return freeze_pinned_native;)
  int res = CAST_TO_FN_PTR(FreezeContFnT, freeze_preempt_entry())(target, 
target->last_Java_sp());
  log_trace(continuations, preempt)("try_preempt: %d", res);
  return res;
}


The following won't be needed:

target->set_pending_jvmti_unmount_event(true);

jvmtiThreadState.cpp:

+  if (thread->pending_jvmti_unmount_event()) {
+    
assert(java_lang_VirtualThread::is_preempted(JNIHandles::resolve(vthread)), 
"should be marked preempted");
+    JvmtiExport::post_vthread_unmount(vthread);
+    thread->set_pending_jvmti_unmount_event(false);
+  }


As we discussed before there can be the `has_async_exception_condition()` flag 
set after a VTMS unmount transition has been started. But there is always such 
a race in VTMS transitions and the flag has to be processed as usual.

src/hotspot/share/runtime/objectMonitor.cpp line 1643:

> 1641:       // actual callee (see nmethod::preserve_callee_argument_oops()).
> 1642:       ThreadOnMonitorWaitedEvent tmwe(current);
> 1643:       JvmtiExport::vthread_post_monitor_waited(current, node->_monitor, 
> timed_out);

We post a JVMTI `MonitorWaited` event here for a virtual thread.
A couple of questions on this:
- Q1: Is this posted after the `VirtualThreadMount` extension event posted?
         Unfortunately, it is not easy to make this conclusion.
- Q2: The `JvmtiExport::post_monitor_waited()` is called at the line 1801.
          Does it post the `MonitorWaited` event for this virtual thread as 
well?
          If not, then it is not clear how posting for virtual thread is 
avoided.

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

PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1820012783
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1820052049
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1820062505
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1822235309
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1822224512
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1828376585
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1829199889

Reply via email to