< prev index next >

src/hotspot/share/runtime/objectMonitor.cpp

Print this page
@@ -293,10 +293,11 @@
    _entry_list(nullptr),
    _entry_list_tail(nullptr),
    _succ(NO_OWNER),
    _SpinDuration(ObjectMonitor::Knob_SpinLimit),
    _contentions(0),
+   _unmounted_vthreads(0),
    _wait_set(nullptr),
    _waiters(0),
    _wait_set_lock(0),
    _stack_locker(nullptr)
  { }

@@ -981,23 +982,22 @@
    // TODO: Defer all thread state transitions until park-time.
    // Since state transitions are heavy and inefficient we'd like
    // to defer the state transitions until absolutely necessary,
    // and in doing so avoid some transitions ...
  
-   // For virtual threads that are pinned, do a timed-park instead to
-   // alleviate some deadlocks cases where the succesor is an unmounted
-   // virtual thread that cannot run. This can happen in particular when
-   // this virtual thread is currently loading/initializing a class, and
-   // all other carriers have a vthread pinned to it waiting for said class
-   // to be loaded/initialized.
+   // If there are unmounted virtual threads in the _entry_list do a timed-park
+   // instead to alleviate some deadlocks cases where one of them is picked as
+   // the successor but cannot run due to having run out of carriers. This can
+   // happen, for example, if this is a pinned virtual thread currently loading
+   // or initializining a class, and all other carriers have a pinned vthread
+   // waiting for said class to be loaded/initialized.
+   // Read counter *after* adding this thread to the _entry_list.
+   // Adding to _entry_list uses Atomic::cmpxchg() which already provides
+   // a fence that prevents this load from floating up previous store.
+   bool do_timed_parked = has_unmounted_vthreads();
    static int MAX_RECHECK_INTERVAL = 1000;
    int recheck_interval = 1;
-   bool do_timed_parked = false;
-   ContinuationEntry* ce = current->last_continuation();
-   if (ce != nullptr && ce->is_virtual_thread()) {
-     do_timed_parked = true;
-   }
  
    for (;;) {
  
      if (try_lock(current) == TryLockResult::Success) {
        break;

@@ -1088,10 +1088,28 @@
    assert(currentNode != nullptr, "invariant");
    assert(currentNode->_thread == current, "invariant");
    assert(_waiters > 0, "invariant");
    assert_mark_word_consistency();
  
+   // If there are unmounted virtual threads in the _entry_list do a timed-park
+   // instead to alleviate some deadlocks cases where one of them is picked as
+   // the successor but cannot run due to having run out of carriers. This can
+   // happen, for example, if this is a pinned virtual thread (or plain carrier)
+   // waiting for a class to be initialized.
+   // In theory we only get here in the "notification" case where the thread has
+   // already been added to the _entry_list. But if the thread happened to be interrupted
+   // at the same time it was being notified, we could have read a state of TS_ENTER
+   // that led us here but the thread hasn't been added yet to the queue. In that
+   // case getting a false value from has_unmounted_vthreads() is not a guarantee
+   // that vthreads weren't added before this thread to the _entry_list. We will live
+   // with this corner case not only because it would be very rare, but also because
+   // if there are several carriers blocked in this same situation, this would only
+   // happen for the first one notified.
+   bool do_timed_parked = has_unmounted_vthreads();
+   static int MAX_RECHECK_INTERVAL = 1000;
+   int recheck_interval = 1;
+ 
    for (;;) {
      ObjectWaiter::TStates v = currentNode->TState;
      guarantee(v == ObjectWaiter::TS_ENTER, "invariant");
      assert(!has_owner(current), "invariant");
  

@@ -1112,11 +1130,20 @@
        assert(current->thread_state() == _thread_in_vm, "invariant");
  
        {
          ClearSuccOnSuspend csos(this);
          ThreadBlockInVMPreprocess<ClearSuccOnSuspend> tbivs(current, csos, true /* allow_suspend */);
-         current->_ParkEvent->park();
+         if (do_timed_parked) {
+           current->_ParkEvent->park((jlong) recheck_interval);
+           // Increase the recheck_interval, but clamp the value.
+           recheck_interval *= 8;
+           if (recheck_interval > MAX_RECHECK_INTERVAL) {
+             recheck_interval = MAX_RECHECK_INTERVAL;
+           }
+         } else {
+           current->_ParkEvent->park();
+         }
        }
      }
  
      // Try again, but just so we distinguish between futile wakeups and
      // successful wakeups.  The following test isn't algorithmically

@@ -1159,13 +1186,20 @@
      return true;
    }
  
    oop vthread = current->vthread();
    ObjectWaiter* node = waiter != nullptr ? waiter : new ObjectWaiter(vthread, this);
+ 
+   // Increment counter *before* adding the vthread to the _entry_list.
+   // Adding to _entry_list uses Atomic::cmpxchg() which already provides
+   // a fence that prevents reordering of the stores.
+   inc_unmounted_vthreads();
+ 
    if (try_lock_or_add_to_entry_list(current, node)) {
      // We got the lock.
      if (waiter == nullptr) delete node;  // for Object.wait() don't delete yet
+     dec_unmounted_vthreads();
      return true;
    }
    // This thread is now added to the entry_list.
  
    // We have to try once more since owner could have exited monitor and checked

@@ -1173,10 +1207,11 @@
    if (try_lock(current) == TryLockResult::Success) {
      assert(has_owner(current), "invariant");
      unlink_after_acquire(current, node);
      if (has_successor(current)) clear_successor();
      if (waiter == nullptr) delete node;  // for Object.wait() don't delete yet
+     dec_unmounted_vthreads();
      return true;
    }
  
    assert(java_lang_VirtualThread::state(vthread) == java_lang_VirtualThread::RUNNING, "wrong state for vthread");
    java_lang_VirtualThread::set_state(vthread, java_lang_VirtualThread::BLOCKING);

@@ -1228,10 +1263,11 @@
  }
  
  void ObjectMonitor::vthread_epilog(JavaThread* current, ObjectWaiter* node) {
    assert(has_owner(current), "invariant");
    add_to_contentions(-1);
+   dec_unmounted_vthreads();
  
    if (has_successor(current)) clear_successor();
  
    guarantee(_recursions == 0, "invariant");
  

@@ -1768,11 +1804,11 @@
        JvmtiExport::post_monitor_wait(current, object(), millis);
      }
      current->set_current_waiting_monitor(this);
      result = Continuation::try_preempt(current, ce->cont_oop(current));
      if (result == freeze_ok) {
-       vthread_wait(current, millis);
+       vthread_wait(current, millis, interruptible);
        current->set_current_waiting_monitor(nullptr);
        return;
      }
    }
    // The jtiows does nothing for non-interruptible.

@@ -1998,10 +2034,14 @@
        // there is nothing to do.
        if (old_state == java_lang_VirtualThread::WAIT ||
            old_state == java_lang_VirtualThread::TIMED_WAIT) {
          java_lang_VirtualThread::cmpxchg_state(vthread, old_state, java_lang_VirtualThread::BLOCKED);
        }
+       // Increment counter *before* adding the vthread to the _entry_list.
+       // Adding to _entry_list uses Atomic::cmpxchg() which already provides
+       // a fence that prevents reordering of the stores.
+       inc_unmounted_vthreads();
      }
  
      iterator->_notified = true;
      iterator->_notifier_tid = JFR_THREAD_ID(current);
      did_notify = true;

@@ -2015,10 +2055,23 @@
      // on _wait_set_lock so it's not profitable to reduce the length of the
      // critical section.
  
      if (!iterator->is_vthread()) {
        iterator->wait_reenter_begin(this);
+ 
+       // Read counter *after* adding the thread to the _entry_list.
+       // Adding to _entry_list uses Atomic::cmpxchg() which already provides
+       // a fence that prevents this load from floating up previous store.
+       if (has_unmounted_vthreads()) {
+         // Wake up the thread to alleviate some deadlocks cases where the successor
+         // that will be picked up when this thread releases the monitor is an unmounted
+         // virtual thread that cannot run due to having run out of carriers. Upon waking
+         // up, the thread will call reenter_internal() which will use time-park in case
+         // there is contention and there are still vthreads in the _entry_list.
+         JavaThread* t = iterator->thread();
+         t->_ParkEvent->unpark();
+       }
      }
    }
    Thread::SpinRelease(&_wait_set_lock);
    return did_notify;
  }

@@ -2104,16 +2157,18 @@
    if ((tally > 0) && event.should_commit()) {
      post_monitor_notify_event(&event, this, /* notified_count = */ tally);
    }
  }
  
- void ObjectMonitor::vthread_wait(JavaThread* current, jlong millis) {
+ void ObjectMonitor::vthread_wait(JavaThread* current, jlong millis, bool interruptible) {
    oop vthread = current->vthread();
    ObjectWaiter* node = new ObjectWaiter(vthread, this);
    node->_is_wait = true;
+   node->_interruptible = interruptible;
    node->TState = ObjectWaiter::TS_WAIT;
    java_lang_VirtualThread::set_notified(vthread, false);  // Reset notified flag
+   java_lang_VirtualThread::set_interruptible_wait(vthread, interruptible);
  
    // Enter the waiting queue, which is a circular doubly linked list in this case
    // but it could be a priority queue or any data structure.
    // _wait_set_lock protects the wait queue.  Normally the wait queue is accessed only
    // by the owner of the monitor *except* in the case where park()

@@ -2155,15 +2210,16 @@
    // If this was an interrupted case, set the _interrupted boolean so that
    // once we re-acquire the monitor we know if we need to throw IE or not.
    ObjectWaiter::TStates state = node->TState;
    bool was_notified = state == ObjectWaiter::TS_ENTER;
    assert(was_notified || state == ObjectWaiter::TS_RUN, "");
-   node->_interrupted = !was_notified && current->is_interrupted(false);
+   node->_interrupted = node->_interruptible && !was_notified && current->is_interrupted(false);
  
-   // Post JFR and JVMTI events.
+   // Post JFR and JVMTI events. If non-interruptible we are in
+   // ObjectLocker case so we don't post anything.
    EventJavaMonitorWait wait_event;
-   if (wait_event.should_commit() || JvmtiExport::should_post_monitor_waited()) {
+   if (node->_interruptible && (wait_event.should_commit() || JvmtiExport::should_post_monitor_waited())) {
      vthread_monitor_waited_event(current, node, cont, &wait_event, !was_notified && !node->_interrupted);
    }
  
    // Mark that we are at reenter so that we don't call this method again.
    node->_at_reenter = true;

@@ -2626,10 +2682,11 @@
    st->print_cr("  _entry_list = " INTPTR_FORMAT, p2i(_entry_list));
    st->print_cr("  _entry_list_tail = " INTPTR_FORMAT, p2i(_entry_list_tail));
    st->print_cr("  _succ = " INT64_FORMAT, successor());
    st->print_cr("  _SpinDuration = %d", _SpinDuration);
    st->print_cr("  _contentions = %d", contentions());
+   st->print_cr("  _unmounted_vthreads = " INT64_FORMAT, _unmounted_vthreads);
    st->print_cr("  _wait_set = " INTPTR_FORMAT, p2i(_wait_set));
    st->print_cr("  _waiters = %d", _waiters);
    st->print_cr("  _wait_set_lock = %d", _wait_set_lock);
    st->print_cr("}");
  }
< prev index next >