From 1f3fd3da1d24118a29d28f01d3fa59d7712607e5 Mon Sep 17 00:00:00 2001 From: Anton Artemov Date: Mon, 2 Feb 2026 08:20:00 +0000 Subject: [PATCH] 8366659: ObjectMonitor::wait() liveness problem with a suspension request Co-authored-by: Patricio Chilano Mateo Co-authored-by: Daniel D. Daugherty Reviewed-by: dcubed, sspitsyn, dholmes, pchilanomate --- src/hotspot/share/runtime/objectMonitor.cpp | 226 ++++++----- src/hotspot/share/runtime/objectMonitor.hpp | 19 +- .../SuspendWithObjectMonitorWait.java | 371 ------------------ .../SuspendWithObjectMonitorWaitBase.java | 236 +++++++++++ .../SuspendWithObjectMonitorWaitDefault.java | 138 +++++++ ...WithObjectMonitorWaitReentryPartFirst.java | 152 +++++++ ...ithObjectMonitorWaitReentryPartSecond.java | 162 ++++++++ .../SuspendWithObjectMonitorWaitWorker.java | 128 ++++++ .../libSuspendWithObjectMonitorWait.cpp | 6 +- 9 files changed, 952 insertions(+), 486 deletions(-) delete mode 100644 test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWait.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitBase.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitDefault.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartFirst.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartSecond.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitWorker.java diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp index 785ee2af592..144533cd959 100644 --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, 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 @@ -339,15 +339,6 @@ void ObjectMonitor::ExitOnSuspend::operator()(JavaThread* current) { } } -void ObjectMonitor::ClearSuccOnSuspend::operator()(JavaThread* current) { - if (current->is_suspended()) { - if (_om->has_successor(current)) { - _om->clear_successor(); - OrderAccess::fence(); // always do a full fence when successor is cleared - } - } -} - #define assert_mark_word_consistency() \ assert(UseObjectMonitorTable || object()->mark() == markWord::encode(this), \ "object mark must match encoded this: mark=" INTPTR_FORMAT \ @@ -500,7 +491,7 @@ bool ObjectMonitor::spin_enter(JavaThread* current) { return false; } -bool ObjectMonitor::enter(JavaThread* current) { +bool ObjectMonitor::enter(JavaThread* current, bool post_jvmti_events) { assert(current == JavaThread::current(), "must be"); if (spin_enter(current)) { @@ -521,15 +512,15 @@ bool ObjectMonitor::enter(JavaThread* current) { } // At this point this ObjectMonitor cannot be deflated, finish contended enter - enter_with_contention_mark(current, contention_mark); + enter_with_contention_mark(current, contention_mark, post_jvmti_events); return true; } -void ObjectMonitor::notify_contended_enter(JavaThread* current) { +void ObjectMonitor::notify_contended_enter(JavaThread* current, bool post_jvmti_events) { current->set_current_pending_monitor(this); DTRACE_MONITOR_PROBE(contended__enter, this, object(), current); - if (JvmtiExport::should_post_monitor_contended_enter()) { + if (post_jvmti_events && JvmtiExport::should_post_monitor_contended_enter()) { JvmtiExport::post_monitor_contended_enter(current, this); // The current thread does not yet own the monitor and does not @@ -540,7 +531,7 @@ void ObjectMonitor::notify_contended_enter(JavaThread* current) { } } -void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonitorContentionMark &cm) { +void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonitorContentionMark &cm, bool post_jvmti_events) { assert(current == JavaThread::current(), "must be"); assert(!has_owner(current), "must be"); assert(cm._monitor == this, "must be"); @@ -564,7 +555,7 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito ContinuationEntry* ce = current->last_continuation(); bool is_virtual = ce != nullptr && ce->is_virtual_thread(); if (is_virtual) { - notify_contended_enter(current); + notify_contended_enter(current, post_jvmti_events); result = Continuation::try_preempt(current, ce->cont_oop(current)); if (result == freeze_ok) { bool acquired = vthread_monitor_enter(current); @@ -573,7 +564,7 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito // _entry_list so cancel preemption. We will still go through the preempt stub // but instead of unmounting we will call thaw to continue execution. current->set_preemption_cancelled(true); - if (JvmtiExport::should_post_monitor_contended_entered()) { + if (post_jvmti_events && JvmtiExport::should_post_monitor_contended_entered()) { // We are going to call thaw again after this and finish the VMTS // transition so no need to do it here. We will post the event there. current->set_contended_entered_monitor(this); @@ -610,7 +601,8 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito // states will still report that the thread is blocked trying to // acquire it. // If there is a suspend request, ExitOnSuspend will exit the OM - // and set the OM as pending. + // and set the OM as pending, the thread will not be reported as + // having "-locked" the monitor. } if (!eos.exited()) { // ExitOnSuspend did not exit the OM @@ -644,7 +636,7 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito // spinning we could increment JVMStat counters, etc. DTRACE_MONITOR_PROBE(contended__entered, this, object(), current); - if (JvmtiExport::should_post_monitor_contended_entered()) { + if (post_jvmti_events && JvmtiExport::should_post_monitor_contended_entered()) { JvmtiExport::post_monitor_contended_entered(current, this); // The current thread already owns the monitor and is not going to @@ -1102,11 +1094,10 @@ void ObjectMonitor::enter_internal(JavaThread* current) { void ObjectMonitor::reenter_internal(JavaThread* current, ObjectWaiter* currentNode) { assert(current != nullptr, "invariant"); - assert(current->thread_state() != _thread_blocked, "invariant"); + assert(current->thread_state() == _thread_blocked, "invariant"); assert(currentNode != nullptr, "invariant"); assert(currentNode->_thread == current, "invariant"); assert(_waiters > 0, "invariant"); - assert_mark_word_consistency(); // If there are unmounted virtual threads ahead in the _entry_list we want // to do a timed-park instead to alleviate some deadlock cases where one @@ -1142,22 +1133,15 @@ void ObjectMonitor::reenter_internal(JavaThread* current, ObjectWaiter* currentN { OSThreadContendState osts(current->osthread()); - - assert(current->thread_state() == _thread_in_vm, "invariant"); - - { - ClearSuccOnSuspend csos(this); - ThreadBlockInVMPreprocess tbivs(current, csos, true /* allow_suspend */); - if (do_timed_parked) { - current->_ParkEvent->park(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(); + if (do_timed_parked) { + current->_ParkEvent->park(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(); } } @@ -1184,7 +1168,6 @@ void ObjectMonitor::reenter_internal(JavaThread* current, ObjectWaiter* currentN // Current has acquired the lock -- Unlink current from the _entry_list. assert(has_owner(current), "invariant"); - assert_mark_word_consistency(); unlink_after_acquire(current, currentNode); if (has_successor(current)) clear_successor(); assert(!has_successor(current), "invariant"); @@ -1883,7 +1866,7 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // while (!timeout && !interrupted && node.TState == TS_WAIT) park() int ret = OS_OK; - bool was_notified = false; + bool was_notified = true; // Need to check interrupt state whilst still _thread_in_vm bool interrupted = interruptible && current->is_interrupted(false); @@ -1895,8 +1878,7 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { assert(current->thread_state() == _thread_in_vm, "invariant"); { - ClearSuccOnSuspend csos(this); - ThreadBlockInVMPreprocess tbivs(current, csos, true /* allow_suspend */); + ThreadBlockInVM tbivm(current, false /* allow_suspend */); if (interrupted || HAS_PENDING_EXCEPTION) { // Intentionally empty } else if (node.TState == ObjectWaiter::TS_WAIT) { @@ -1928,17 +1910,16 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { if (node.TState == ObjectWaiter::TS_WAIT) { dequeue_specific_waiter(&node); // unlink from wait_set node.TState = ObjectWaiter::TS_RUN; + was_notified = false; } } - - // The thread is now either on off-list (TS_RUN), + // The thread is now either off-list (TS_RUN), // or on the entry_list (TS_ENTER). // The Node's TState variable is stable from the perspective of this thread. // No other threads will asynchronously modify TState. guarantee(node.TState != ObjectWaiter::TS_WAIT, "invariant"); OrderAccess::loadload(); if (has_successor(current)) clear_successor(); - was_notified = node.TState == ObjectWaiter::TS_ENTER; // Reentry phase -- reacquire the monitor. // re-enter contended monitor after object.wait(). @@ -1947,27 +1928,19 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // although the raw address of the object may have changed. // (Don't cache naked oops over safepoints, of course). - // post monitor waited event. Note that this is past-tense, we are done waiting. - if (JvmtiExport::should_post_monitor_waited()) { - JvmtiExport::post_monitor_waited(current, this, ret == OS_TIMEOUT); + // Post monitor waited event. Note that this is past-tense, we are done waiting. + // An event could have been enabled after notification, in this case + // a thread will have TS_ENTER state and posting the event may hit a suspension point. + // From a debugging perspective, it is more important to have no missing events. + if (interruptible && JvmtiExport::should_post_monitor_waited() && node.TState != ObjectWaiter::TS_ENTER) { - if (was_notified && has_successor(current)) { - // In this part of the monitor wait-notify-reenter protocol it - // is possible (and normal) for another thread to do a fastpath - // monitor enter-exit while this thread is still trying to get - // to the reenter portion of the protocol. - // - // The ObjectMonitor was notified and the current thread is - // the successor which also means that an unpark() has already - // been done. The JVMTI_EVENT_MONITOR_WAITED event handler can - // consume the unpark() that was done when the successor was - // set because the same ParkEvent is shared between Java - // monitors and JVM/TI RawMonitors (for now). - // - // We redo the unpark() to ensure forward progress, i.e., we - // don't want all pending threads hanging (parked) with none - // entering the unlocked monitor. - current->_ParkEvent->unpark(); + // Process suspend requests now if any, before posting the event. + { + ThreadBlockInVM tbvm(current, true); + } + // Re-check the condition as the monitor waited events can be disabled whilst thread was suspended. + if (JvmtiExport::should_post_monitor_waited()) { + JvmtiExport::post_monitor_waited(current, this, ret == OS_TIMEOUT); } } @@ -1986,8 +1959,30 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { NoPreemptMark npm(current); enter(current); } else { + // This means the thread has been un-parked and added to the entry_list + // in notify_internal, i.e. notified while waiting. guarantee(v == ObjectWaiter::TS_ENTER, "invariant"); - reenter_internal(current, &node); + ExitOnSuspend eos(this); + { + ThreadBlockInVMPreprocess tbivs(current, eos, true /* allow_suspend */); + reenter_internal(current, &node); + // We can go to a safepoint at the end of this block. If we + // do a thread dump during that safepoint, then this thread will show + // as having "-locked" the monitor, but the OS and java.lang.Thread + // states will still report that the thread is blocked trying to + // acquire it. + // If there is a suspend request, ExitOnSuspend will exit the OM + // and set the OM as pending, the thread will not be reported as + // having "-locked" the monitor. + } + if (eos.exited()) { + // ExitOnSuspend exit the OM + assert(!has_owner(current), "invariant"); + guarantee(node.TState == ObjectWaiter::TS_RUN, "invariant"); + current->set_current_pending_monitor(nullptr); + enter(current, false /* post_jvmti_events */); + } + assert(has_owner(current), "invariant"); node.wait_reenter_end(this); } @@ -2041,6 +2036,8 @@ bool ObjectMonitor::notify_internal(JavaThread* current) { ObjectWaiter* iterator = dequeue_waiter(); if (iterator != nullptr) { guarantee(iterator->TState == ObjectWaiter::TS_WAIT, "invariant"); + iterator->_notifier_tid = JFR_THREAD_ID(current); + did_notify = true; if (iterator->is_vthread()) { oop vthread = iterator->vthread(); @@ -2056,45 +2053,55 @@ bool ObjectMonitor::notify_internal(JavaThread* current) { 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(); + if (!JvmtiExport::should_post_monitor_waited()) { + // 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(); + add_to_entry_list(current, iterator); + } else { + iterator->TState = ObjectWaiter::TS_RUN; + if (java_lang_VirtualThread::set_onWaitingList(vthread, vthread_list_head())) { + ParkEvent* pe = ObjectMonitor::vthread_unparker_ParkEvent(); + pe->unpark(); + } + } + } else { + if (!JvmtiExport::should_post_monitor_waited()) { + add_to_entry_list(current, iterator); + // 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 deadlock 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 timed-park in case + // there is contention and there are still vthreads in the _entry_list. + // If the target was interrupted or the wait timed-out at the same time, it could + // have reached reenter_internal and read a false value of has_unmounted_vthreads() + // before we added it to the _entry_list above. To deal with that case, we set _do_timed_park + // which will be read by the target on the next loop iteration in reenter_internal. + iterator->_do_timed_park = true; + JavaThread* t = iterator->thread(); + t->_ParkEvent->unpark(); + } + iterator->wait_reenter_begin(this); + } else { + iterator->TState = ObjectWaiter::TS_RUN; + JavaThread* t = iterator->thread(); + assert(t != nullptr, ""); + t->_ParkEvent->unpark(); + } } - iterator->_notifier_tid = JFR_THREAD_ID(current); - did_notify = true; - add_to_entry_list(current, iterator); - // _wait_set_lock protects the wait queue, not the entry_list. We could // move the add-to-entry_list operation, above, outside the critical section // protected by _wait_set_lock. In practice that's not useful. With the - // exception of wait() timeouts and interrupts the monitor owner + // exception of wait() timeouts and interrupts the monitor owner // is the only thread that grabs _wait_set_lock. There's almost no contention // 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 deadlock 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 timed-park in case - // there is contention and there are still vthreads in the _entry_list. - // If the target was interrupted or the wait timed-out at the same time, it could - // have reached reenter_internal and read a false value of has_unmounted_vthreads() - // before we added it to the _entry_list above. To deal with that case, we set _do_timed_park - // which will be read by the target on the next loop iteration in reenter_internal. - iterator->_do_timed_park = true; - JavaThread* t = iterator->thread(); - t->_ParkEvent->unpark(); - } - } } return did_notify; } @@ -2221,19 +2228,22 @@ bool ObjectMonitor::vthread_wait_reenter(JavaThread* current, ObjectWaiter* node // The first time we run after being preempted on Object.wait() we // need to check if we were interrupted or the wait timed-out, and // in that case remove ourselves from the _wait_set queue. + bool was_notified = true; if (node->TState == ObjectWaiter::TS_WAIT) { SpinCriticalSection scs(&_wait_set_lock); if (node->TState == ObjectWaiter::TS_WAIT) { dequeue_specific_waiter(node); // unlink from wait_set node->TState = ObjectWaiter::TS_RUN; + was_notified = false; } } // 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, ""); + assert(was_notified || state == ObjectWaiter::TS_RUN, + "was not notified and is not in the right state: state = %s", + node->getTStateName(state)); node->_interrupted = node->_interruptible && !was_notified && current->is_interrupted(false); // Post JFR and JVMTI events. If non-interruptible we are in @@ -2246,7 +2256,10 @@ bool ObjectMonitor::vthread_wait_reenter(JavaThread* current, ObjectWaiter* node // Mark that we are at reenter so that we don't call this method again. node->_at_reenter = true; - if (!was_notified) { + // We check the state rather than was_notified because, when JVMTI + // monitor_waited event is enabled, the notifier only unparks the waiter + // without adding it to the entry_list. + if (state == ObjectWaiter::TS_RUN) { bool acquired = vthread_monitor_enter(current, node); if (acquired) { guarantee(_recursions == 0, "invariant"); @@ -2537,6 +2550,23 @@ ObjectWaiter::ObjectWaiter(JavaThread* current) { _active = false; } +const char* ObjectWaiter::getTStateName(ObjectWaiter::TStates state) { + switch (state) { + case ObjectWaiter::TS_UNDEF: + return "TS_UNDEF"; + case ObjectWaiter::TS_READY: + return "TS_READY"; + case ObjectWaiter::TS_RUN: + return "TS_RUN"; + case ObjectWaiter::TS_WAIT: + return "TS_WAIT"; + case ObjectWaiter::TS_ENTER: + return "TS_ENTER"; + default: + ShouldNotReachHere(); + } +} + ObjectWaiter::ObjectWaiter(oop vthread, ObjectMonitor* mon) : ObjectWaiter(nullptr) { assert(oopDesc::is_oop(vthread), ""); _vthread = OopHandle(JavaThread::thread_oop_storage(), vthread); diff --git a/src/hotspot/share/runtime/objectMonitor.hpp b/src/hotspot/share/runtime/objectMonitor.hpp index 53b64f1e8a5..574a652f230 100644 --- a/src/hotspot/share/runtime/objectMonitor.hpp +++ b/src/hotspot/share/runtime/objectMonitor.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, 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 @@ -40,7 +40,6 @@ class ParkEvent; class BasicLock; class ContinuationWrapper; - class ObjectWaiter : public CHeapObj { public: enum TStates : uint8_t { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER }; @@ -72,7 +71,7 @@ class ObjectWaiter : public CHeapObj { oop vthread() const; void wait_reenter_begin(ObjectMonitor *mon); void wait_reenter_end(ObjectMonitor *mon); - + const char* getTStateName(TStates state); void set_bad_pointers() { #ifdef ASSERT this->_prev = (ObjectWaiter*) badAddressVal; @@ -352,7 +351,6 @@ class ObjectMonitor : public CHeapObj { // returns false and throws IllegalMonitorStateException (IMSE). bool check_owner(TRAPS); - private: class ExitOnSuspend { protected: ObjectMonitor* _om; @@ -362,23 +360,16 @@ class ObjectMonitor : public CHeapObj { void operator()(JavaThread* current); bool exited() { return _om_exited; } }; - class ClearSuccOnSuspend { - protected: - ObjectMonitor* _om; - public: - ClearSuccOnSuspend(ObjectMonitor* om) : _om(om) {} - void operator()(JavaThread* current); - }; bool enter_is_async_deflating(); - void notify_contended_enter(JavaThread *current); + void notify_contended_enter(JavaThread *current, bool post_jvmti_events = true); public: void enter_for_with_contention_mark(JavaThread* locking_thread, ObjectMonitorContentionMark& contention_mark); bool enter_for(JavaThread* locking_thread); - bool enter(JavaThread* current); + bool enter(JavaThread* current, bool post_jvmti_events = true); bool try_enter(JavaThread* current, bool check_for_recursion = true); bool spin_enter(JavaThread* current); - void enter_with_contention_mark(JavaThread* current, ObjectMonitorContentionMark& contention_mark); + void enter_with_contention_mark(JavaThread* current, ObjectMonitorContentionMark& contention_mark, bool post_jvmti_events = true); void exit(JavaThread* current, bool not_suspended = true); bool resume_operation(JavaThread* current, ObjectWaiter* node, ContinuationWrapper& cont); void wait(jlong millis, bool interruptible, TRAPS); diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWait.java b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWait.java deleted file mode 100644 index 3a747a3e86b..00000000000 --- a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWait.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (c) 2001, 2021, 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 - * @bug 4413752 8262881 - * @summary Test SuspendThread with ObjectMonitor wait. - * @requires vm.jvmti - * @library /test/lib - * @compile SuspendWithObjectMonitorWait.java - * @run main/othervm/native -agentlib:SuspendWithObjectMonitorWait SuspendWithObjectMonitorWait - */ - -import java.io.PrintStream; - -// -// main waiter resumer -// ================= ================== =================== -// launch waiter -// waiter running -// launch resumer enter threadLock -// threadLock.wait() resumer running -// enter threadLock : wait for notify -// threadLock.notify wait finishes : -// : reenter blocks : -// suspend waiter : -// exit threadLock : : -// : : -// : : : -// notify resumer : wait finishes -// join resumer : enter threadLock -// : resume waiter -// : : exit threadLock -// : reenter threadLock : -// : resumer exits -// join waiter : -// waiter exits -// - -public class SuspendWithObjectMonitorWait { - private static final String AGENT_LIB = "SuspendWithObjectMonitorWait"; - private static final int exit_delta = 95; - - private static final int DEF_TIME_MAX = 60; // default max # secs to test - private static final int JOIN_MAX = 30; // max # secs to wait for join - - public static final int TS_INIT = 1; // initial testState - public static final int TS_WAITER_RUNNING = 2; // waiter is running - public static final int TS_RESUMER_RUNNING = 3; // resumer is running - public static final int TS_READY_TO_NOTIFY = 4; // ready to notify threadLock - public static final int TS_CALL_SUSPEND = 5; // call suspend on contender - public static final int TS_READY_TO_RESUME = 6; // ready to resume waiter - public static final int TS_CALL_RESUME = 7; // call resume on waiter - public static final int TS_WAITER_DONE = 8; // waiter has run; done - - public static Object barrierLaunch = new Object(); // controls thread launch - public static Object barrierResumer = new Object(); // controls resumer - public static Object threadLock = new Object(); // testing object - - public static long count = 0; - public static boolean printDebug = false; - public volatile static int testState; - - private static void log(String msg) { System.out.println(msg); } - - native static int suspendThread(SuspendWithObjectMonitorWaitWorker thr); - native static int wait4ContendedEnter(SuspendWithObjectMonitorWaitWorker thr); - - public static void main(String[] args) throws Exception { - try { - System.loadLibrary(AGENT_LIB); - log("Loaded library: " + AGENT_LIB); - } catch (UnsatisfiedLinkError ule) { - log("Failed to load library: " + AGENT_LIB); - log("java.library.path: " + System.getProperty("java.library.path")); - throw ule; - } - - int timeMax = 0; - if (args.length == 0) { - timeMax = DEF_TIME_MAX; - } else { - int argIndex = 0; - int argsLeft = args.length; - if (args[0].equals("-p")) { - printDebug = true; - argIndex = 1; - argsLeft--; - } - if (argsLeft == 0) { - timeMax = DEF_TIME_MAX; - } else if (argsLeft == 1) { - try { - timeMax = Integer.parseUnsignedInt(args[argIndex]); - } catch (NumberFormatException nfe) { - System.err.println("'" + args[argIndex] + - "': invalid timeMax value."); - usage(); - } - } else { - usage(); - } - } - - System.exit(run(timeMax, System.out) + exit_delta); - } - - public static void logDebug(String mesg) { - if (printDebug) { - System.err.println(Thread.currentThread().getName() + ": " + mesg); - } - } - - public static void usage() { - System.err.println("Usage: " + AGENT_LIB + " [-p][time_max]"); - System.err.println("where:"); - System.err.println(" -p ::= print debug info"); - System.err.println(" time_max ::= max looping time in seconds"); - System.err.println(" (default is " + DEF_TIME_MAX + - " seconds)"); - System.exit(1); - } - - public static int run(int timeMax, PrintStream out) { - return (new SuspendWithObjectMonitorWait()).doWork(timeMax, out); - } - - public static void checkTestState(int exp) { - if (testState != exp) { - System.err.println("Failure at " + count + " loops."); - throw new InternalError("Unexpected test state value: " - + "expected=" + exp + " actual=" + testState); - } - } - - public int doWork(int timeMax, PrintStream out) { - SuspendWithObjectMonitorWaitWorker waiter; // waiter thread - SuspendWithObjectMonitorWaitWorker resumer; // resumer thread - - System.out.println("About to execute for " + timeMax + " seconds."); - - long start_time = System.currentTimeMillis(); - while (System.currentTimeMillis() < start_time + (timeMax * 1000)) { - count++; - testState = TS_INIT; // starting the test loop - - // launch the waiter thread - synchronized (barrierLaunch) { - waiter = new SuspendWithObjectMonitorWaitWorker("waiter"); - waiter.start(); - - while (testState != TS_WAITER_RUNNING) { - try { - barrierLaunch.wait(0); // wait until it is running - } catch (InterruptedException ex) { - } - } - } - - // launch the resumer thread - synchronized (barrierLaunch) { - resumer = new SuspendWithObjectMonitorWaitWorker("resumer", waiter); - resumer.start(); - - while (testState != TS_RESUMER_RUNNING) { - try { - barrierLaunch.wait(0); // wait until it is running - } catch (InterruptedException ex) { - } - } - } - - checkTestState(TS_RESUMER_RUNNING); - - // The waiter thread was synchronized on threadLock before it - // set TS_WAITER_RUNNING and notified barrierLaunch above so - // we cannot enter threadLock until the waiter thread calls - // threadLock.wait(). - synchronized (threadLock) { - // notify waiter thread so it can try to reenter threadLock - testState = TS_READY_TO_NOTIFY; - threadLock.notify(); - - // wait for the waiter thread to block - logDebug("before contended enter wait"); - int retCode = wait4ContendedEnter(waiter); - if (retCode != 0) { - throw new RuntimeException("error in JVMTI GetThreadState: " - + "retCode=" + retCode); - } - logDebug("done contended enter wait"); - - checkTestState(TS_READY_TO_NOTIFY); - testState = TS_CALL_SUSPEND; - logDebug("before suspend thread"); - retCode = suspendThread(waiter); - if (retCode != 0) { - throw new RuntimeException("error in JVMTI SuspendThread: " - + "retCode=" + retCode); - } - logDebug("suspended thread"); - } - - // - // At this point, all of the child threads are running - // and we can get to meat of the test: - // - // - suspended threadLock waiter (trying to reenter) - // - a threadLock enter in the resumer thread - // - resumption of the waiter thread - // - a threadLock enter in the freshly resumed waiter thread - // - - synchronized (barrierResumer) { - checkTestState(TS_CALL_SUSPEND); - - // tell resumer thread to resume waiter thread - testState = TS_READY_TO_RESUME; - barrierResumer.notify(); - - // Can't call checkTestState() here because the - // resumer thread may have already resumed the - // waiter thread. - } - - try { - resumer.join(JOIN_MAX * 1000); - if (resumer.isAlive()) { - System.err.println("Failure at " + count + " loops."); - throw new InternalError("resumer thread is stuck"); - } - waiter.join(JOIN_MAX * 1000); - if (waiter.isAlive()) { - System.err.println("Failure at " + count + " loops."); - throw new InternalError("waiter thread is stuck"); - } - } catch (InterruptedException ex) { - } - - checkTestState(TS_WAITER_DONE); - } - - System.out.println("Executed " + count + " loops in " + timeMax + - " seconds."); - - return 0; - } -} - -class SuspendWithObjectMonitorWaitWorker extends Thread { - private SuspendWithObjectMonitorWaitWorker target; // target for resume operation - - public SuspendWithObjectMonitorWaitWorker(String name) { - super(name); - } - - public SuspendWithObjectMonitorWaitWorker(String name, SuspendWithObjectMonitorWaitWorker target) { - super(name); - this.target = target; - } - - native static int resumeThread(SuspendWithObjectMonitorWaitWorker thr); - - public void run() { - SuspendWithObjectMonitorWait.logDebug("thread running"); - - // - // Launch the waiter thread: - // - grab the threadLock - // - threadLock.wait() - // - releases threadLock - // - if (getName().equals("waiter")) { - // grab threadLock before we tell main we are running - SuspendWithObjectMonitorWait.logDebug("before enter threadLock"); - synchronized(SuspendWithObjectMonitorWait.threadLock) { - SuspendWithObjectMonitorWait.logDebug("enter threadLock"); - - SuspendWithObjectMonitorWait.checkTestState(SuspendWithObjectMonitorWait.TS_INIT); - - synchronized(SuspendWithObjectMonitorWait.barrierLaunch) { - // tell main we are running - SuspendWithObjectMonitorWait.testState = SuspendWithObjectMonitorWait.TS_WAITER_RUNNING; - SuspendWithObjectMonitorWait.barrierLaunch.notify(); - } - - SuspendWithObjectMonitorWait.logDebug("before wait"); - - // TS_READY_TO_NOTIFY is set after the main thread has - // entered threadLock so a spurious wakeup can't get the - // waiter thread out of this threadLock.wait(0) call: - while (SuspendWithObjectMonitorWait.testState <= SuspendWithObjectMonitorWait.TS_READY_TO_NOTIFY) { - try { - SuspendWithObjectMonitorWait.threadLock.wait(0); - } catch (InterruptedException ex) { - } - } - - SuspendWithObjectMonitorWait.logDebug("after wait"); - - SuspendWithObjectMonitorWait.checkTestState(SuspendWithObjectMonitorWait.TS_CALL_RESUME); - SuspendWithObjectMonitorWait.testState = SuspendWithObjectMonitorWait.TS_WAITER_DONE; - - SuspendWithObjectMonitorWait.logDebug("exit threadLock"); - } - } - // - // Launch the resumer thread: - // - tries to grab the threadLock (should not block!) - // - grabs threadLock - // - resumes the waiter thread - // - releases threadLock - // - else if (getName().equals("resumer")) { - synchronized(SuspendWithObjectMonitorWait.barrierResumer) { - synchronized(SuspendWithObjectMonitorWait.barrierLaunch) { - // tell main we are running - SuspendWithObjectMonitorWait.testState = SuspendWithObjectMonitorWait.TS_RESUMER_RUNNING; - SuspendWithObjectMonitorWait.barrierLaunch.notify(); - } - SuspendWithObjectMonitorWait.logDebug("thread waiting"); - while (SuspendWithObjectMonitorWait.testState != SuspendWithObjectMonitorWait.TS_READY_TO_RESUME) { - try { - // wait for main to tell us when to continue - SuspendWithObjectMonitorWait.barrierResumer.wait(0); - } catch (InterruptedException ex) { - } - } - } - - SuspendWithObjectMonitorWait.logDebug("before enter threadLock"); - synchronized(SuspendWithObjectMonitorWait.threadLock) { - SuspendWithObjectMonitorWait.logDebug("enter threadLock"); - - SuspendWithObjectMonitorWait.checkTestState(SuspendWithObjectMonitorWait.TS_READY_TO_RESUME); - SuspendWithObjectMonitorWait.testState = SuspendWithObjectMonitorWait.TS_CALL_RESUME; - - // resume the waiter thread so waiter.join() can work - SuspendWithObjectMonitorWait.logDebug("before resume thread"); - int retCode = resumeThread(target); - if (retCode != 0) { - throw new RuntimeException("error in JVMTI ResumeThread: " + - "retCode=" + retCode); - } - SuspendWithObjectMonitorWait.logDebug("resumed thread"); - - SuspendWithObjectMonitorWait.logDebug("exit threadLock"); - } - } - } -} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitBase.java b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitBase.java new file mode 100644 index 00000000000..5443e4005fe --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitBase.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2001, 2026, 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. + */ + +import java.io.PrintStream; + +public class SuspendWithObjectMonitorWaitBase { + protected static final String AGENT_LIB = "SuspendWithObjectMonitorWait"; + protected static final int exit_delta = 95; + + protected static final int DEF_TIME_MAX = 60; // default max # secs to test + protected static final int JOIN_MAX = 30; // max # secs to wait for join + + public static final int TS_INIT = 1; // initial testState + public static final int TS_WAITER_RUNNING = 2; // waiter is running + public static final int TS_RESUMER_RUNNING = 3; // resumer is running + public static final int TS_READY_TO_NOTIFY = 4; // ready to notify threadLock + public static final int TS_CALL_SUSPEND = 5; // call suspend on contender + public static final int TS_READY_TO_RESUME = 6; // ready to resume waiter + public static final int TS_CALL_RESUME = 7; // call resume on waiter + public static final int TS_WAITER_DONE = 8; // waiter has run; done + + public static Object barrierLaunch = new Object(); // controls thread launch + public static Object barrierResumer = new Object(); // controls resumer + public static Object threadLock = new Object(); // testing object + + public static long count = 0; + public static boolean printDebug = false; + public volatile static int testState; + + protected static void log(String msg) { System.out.println(msg); } + + native static int suspendThread(SuspendWithObjectMonitorWaitWorker thr); + native static int wait4ContendedEnter(SuspendWithObjectMonitorWaitWorker thr); + + public static void logDebug(String mesg) { + if (printDebug) { + System.err.println(Thread.currentThread().getName() + ": " + mesg); + } + } + + public static void usage() { + System.err.println("Usage: " + AGENT_LIB + " test_case [-p] [time_max]"); + System.err.println("where:"); + System.err.println(" test_case ::= 1 | 2 | 3"); + System.err.println(" -p ::= print debug info"); + System.err.println(" time_max ::= max looping time in seconds"); + System.err.println(" (default is " + DEF_TIME_MAX + + " seconds)"); + System.exit(1); + } + + public static void checkTestState(int exp) { + if (testState != exp) { + System.err.println("Failure at " + count + " loops."); + throw new InternalError("Unexpected test state value: " + + "expected=" + exp + " actual=" + testState); + } + } + + public SuspendWithObjectMonitorWaitWorker launchWaiter(long waitTimeout) { + SuspendWithObjectMonitorWaitWorker waiter; + // launch the waiter thread + synchronized (barrierLaunch) { + waiter = new SuspendWithObjectMonitorWaitWorker("waiter", waitTimeout); + waiter.start(); + + while (testState != TS_WAITER_RUNNING) { + try { + barrierLaunch.wait(0); // wait until it is running + } catch (InterruptedException ex) { + } + } + } + return waiter; + } + + public SuspendWithObjectMonitorWaitWorker launchResumer(SuspendWithObjectMonitorWaitWorker waiter) { + SuspendWithObjectMonitorWaitWorker resumer; + synchronized (barrierLaunch) { + resumer = new SuspendWithObjectMonitorWaitWorker("resumer", waiter); + resumer.start(); + + while (testState != TS_RESUMER_RUNNING) { + try { + barrierLaunch.wait(0); // wait until it is running + } catch (InterruptedException ex) { + } + } + } + return resumer; + } + + public void barrierResumerNotify() { + synchronized (barrierResumer) { + checkTestState(TS_CALL_SUSPEND); + + // tell resumer thread to resume waiter thread + testState = TS_READY_TO_RESUME; + barrierResumer.notify(); + + // Can't call checkTestState() here because the + // resumer thread may have already resumed the + // waiter thread. + } + } + + public void shutDown(SuspendWithObjectMonitorWaitWorker resumer, SuspendWithObjectMonitorWaitWorker waiter) { + try { + resumer.join(JOIN_MAX * 1000); + if (resumer.isAlive()) { + System.err.println("Failure at " + count + " loops."); + throw new InternalError("resumer thread is stuck"); + } + waiter.join(JOIN_MAX * 1000); + if (waiter.isAlive()) { + System.err.println("Failure at " + count + " loops."); + throw new InternalError("waiter thread is stuck"); + } + } catch (InterruptedException ex) { + } + } + + public int run(int timeMax, PrintStream out) { + return 0; + } + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println("Invalid number of arguments, there should be at least a test_case given."); + usage(); + } + + if (args.length > 3) { + System.err.println("Invalid number of arguments, there are too many arguments."); + usage(); + } + + try { + System.loadLibrary(AGENT_LIB); + log("Loaded library: " + AGENT_LIB); + } catch (UnsatisfiedLinkError ule) { + log("Failed to load library: " + AGENT_LIB); + log("java.library.path: " + System.getProperty("java.library.path")); + throw ule; + } + + int testCase = 0; + int timeMax = 0; + for (int argIndex = 0; argIndex < args.length; argIndex++) { + if (args[argIndex].equals("-p")) { + // Handle optional -p arg regardless of position. + printDebug = true; + continue; + } + + if (testCase == 0) { + try { + // testCase must be the first non-optional arg. + testCase = Integer.parseUnsignedInt(args[argIndex]); + log("testCase = " + testCase); + } catch (NumberFormatException nfe) { + System.err.println("'" + args[argIndex] + + "': invalid test_case value."); + usage(); + } + if (testCase < 1 || testCase > 3) { + System.err.println("Invalid test_case value: '" + testCase + "'"); + usage(); + } + continue; + } + + if (argIndex < args.length) { + // timeMax is an optional arg. + try { + timeMax = Integer.parseUnsignedInt(args[argIndex]); + } catch (NumberFormatException nfe) { + System.err.println("'" + args[argIndex] + + "': invalid time_max value."); + usage(); + } + } else { + timeMax = DEF_TIME_MAX; + } + } + + if (timeMax == 0) { + timeMax = DEF_TIME_MAX; + } + log("timeMax = " + timeMax); + + if (testCase == 0) { + // Just -p was given. + System.err.println("Invalid number of arguments, no test_case given."); + usage(); + } + + SuspendWithObjectMonitorWaitBase test = null; + switch (testCase) { + case 1: + test = new SuspendWithObjectMonitorWaitDefault(); + break; + case 2: + test = new SuspendWithObjectMonitorWaitReentryPartFirst(); + break; + case 3: + test = new SuspendWithObjectMonitorWaitReentryPartSecond(); + break; + default: + // Impossible + break; + } + int result = test.run(timeMax, System.out); + System.exit(result + exit_delta); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitDefault.java b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitDefault.java new file mode 100644 index 00000000000..b2c1f108de5 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitDefault.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2001, 2026, 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 + * @bug 4413752 8262881 + * @summary Test SuspendThread with ObjectMonitor wait. + * @requires vm.jvmti + * @library /test/lib + * @compile SuspendWithObjectMonitorWaitDefault.java + * @run main/othervm/native -agentlib:SuspendWithObjectMonitorWait SuspendWithObjectMonitorWaitDefault 1 + */ + +import java.io.PrintStream; + +// +// SuspendWithObjectMonitorWaitDefault algorithm: +// +// main waiter resumer +// ================= ================== =================== +// launch waiter +// waiter running +// launch resumer enter threadLock +// threadLock.wait() resumer running +// enter threadLock : wait for notify +// threadLock.notify wait finishes : +// : reenter blocks : +// suspend waiter : +// exit threadLock : : +// : : +// : : : +// notify resumer : wait finishes +// join resumer : enter threadLock +// : resume waiter +// : : exit threadLock +// : reenter threadLock : +// : resumer exits +// join waiter : +// waiter exits +// + +public class SuspendWithObjectMonitorWaitDefault extends SuspendWithObjectMonitorWaitBase { + + @Override + public int run(int timeMax, PrintStream out) { + return doWork1(timeMax, out); + } + + // Default scenario, the resumer thread is always able to grab the threadLock once notified by the main thread. + public int doWork1(int timeMax, PrintStream out) { + SuspendWithObjectMonitorWaitWorker waiter; // waiter thread + SuspendWithObjectMonitorWaitWorker resumer; // resumer thread + + System.out.println("Test 1: About to execute for " + timeMax + " seconds."); + + long start_time = System.currentTimeMillis(); + while (System.currentTimeMillis() < start_time + (timeMax * 1000)) { + count++; + testState = TS_INIT; // starting the test loop + + // launch the waiter thread + waiter = launchWaiter(0); + + // launch the resumer thread + resumer = launchResumer(waiter); + + checkTestState(TS_RESUMER_RUNNING); + + // The waiter thread was synchronized on threadLock before it + // set TS_WAITER_RUNNING and notified barrierLaunch above so + // we cannot enter threadLock until the waiter thread calls + // threadLock.wait(). + synchronized (threadLock) { + // notify waiter thread so it can try to reenter threadLock + testState = TS_READY_TO_NOTIFY; + threadLock.notify(); + + // wait for the waiter thread to block + logDebug("before contended enter wait"); + int retCode = wait4ContendedEnter(waiter); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI GetThreadState: " + + "retCode=" + retCode); + } + logDebug("done contended enter wait"); + + checkTestState(TS_READY_TO_NOTIFY); + testState = TS_CALL_SUSPEND; + logDebug("before suspend thread"); + retCode = suspendThread(waiter); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI SuspendThread: " + + "retCode=" + retCode); + } + logDebug("suspended thread"); + } + + // + // At this point, all of the child threads are running + // and we can get to meat of the test: + // + // - suspended threadLock waiter (trying to reenter) + // - a threadLock enter in the resumer thread + // - resumption of the waiter thread + // - a threadLock enter in the freshly resumed waiter thread + // + barrierResumerNotify(); + + shutDown(waiter ,resumer); + checkTestState(TS_WAITER_DONE); + } + + System.out.println("Executed " + count + " loops in " + timeMax + + " seconds."); + + return 0; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartFirst.java b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartFirst.java new file mode 100644 index 00000000000..b331b338f47 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartFirst.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2001, 2026, 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 + * @bug 4413752 8262881 + * @summary Test SuspendThread with ObjectMonitor wait. + * @requires vm.jvmti + * @library /test/lib + * @compile SuspendWithObjectMonitorWaitReentryPartFirst.java + * @run main/othervm/native -agentlib:SuspendWithObjectMonitorWait SuspendWithObjectMonitorWaitReentryPartFirst 2 + */ + +import java.io.PrintStream; + +// +// SuspendWithObjectMonitorWaitReentryPartFirst algorithm: +// +// main waiter resumer +// ================= ================== =================== +// launch waiter +// waiter running +// launch resumer enter threadLock +// threadLock.wait() resumer running +// enter threadLock : wait for notify +// threadLock.notify wait finishes : +// : reenter blocks : +// suspend waiter : +// : : +// : : : +// notify resumer : wait finishes +// delay 1-second : : +// exit threadLock : : +// join resumer : enter threadLock +// : resume waiter +// : : exit threadLock +// : reenter threadLock : +// : resumer exits +// join waiter : +// waiter exits +// +// Note: The sleep(1-second) in main along with the delayed exit +// of threadLock in main forces the resumer thread to reach +// "enter threadLock" and block. This difference from the default scenario +// forces the resumer thread to be contending for threadLock +// while the waiter thread is in threadLock.wait() increasing +// stress on the monitor sub-system. +// + +public class SuspendWithObjectMonitorWaitReentryPartFirst extends SuspendWithObjectMonitorWaitBase { + + @Override + public int run(int timeMax, PrintStream out) { + return doWork2(timeMax, out); + } + + // Notify the resumer while holding the threadLock. + public int doWork2(int timeMax, PrintStream out) { + SuspendWithObjectMonitorWaitWorker waiter; // waiter thread + SuspendWithObjectMonitorWaitWorker resumer; // resumer thread + + System.out.println("Test 2: About to execute for " + timeMax + " seconds."); + + long start_time = System.currentTimeMillis(); + while (System.currentTimeMillis() < start_time + (timeMax * 1000)) { + count++; + testState = TS_INIT; // starting the test loop + + // launch the waiter thread + waiter = launchWaiter(0); + + // launch the resumer thread + resumer = launchResumer(waiter); + + checkTestState(TS_RESUMER_RUNNING); + + // The waiter thread was synchronized on threadLock before it + // set TS_WAITER_RUNNING and notified barrierLaunch above so + // we cannot enter threadLock until the waiter thread calls + // threadLock.wait(). + synchronized (threadLock) { + // notify waiter thread so it can try to reenter threadLock + testState = TS_READY_TO_NOTIFY; + threadLock.notify(); + + // wait for the waiter thread to block + logDebug("before contended enter wait"); + int retCode = wait4ContendedEnter(waiter); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI GetThreadState: " + + "retCode=" + retCode); + } + logDebug("done contended enter wait"); + + checkTestState(TS_READY_TO_NOTIFY); + testState = TS_CALL_SUSPEND; + logDebug("before suspend thread"); + retCode = suspendThread(waiter); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI SuspendThread: " + + "retCode=" + retCode); + } + logDebug("suspended thread"); + + // + // At this point, all of the child threads are running + // and we can get to meat of the test: + // + // - suspended threadLock waiter (trying to reenter) + // - a blocked threadLock enter in the resumer thread while the + // threadLock is held by the main thread. + // - resumption of the waiter thread + // - a threadLock enter in the freshly resumed waiter thread + // + barrierResumerNotify(); + try { + // Delay for 1-second while holding the threadLock to force the + // resumer thread to block on entering the threadLock. + Thread.sleep(1000); + } catch (Exception e) {} + } + + shutDown(waiter ,resumer); + checkTestState(TS_WAITER_DONE); + } + + System.out.println("Executed " + count + " loops in " + timeMax + + " seconds."); + + return 0; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartSecond.java b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartSecond.java new file mode 100644 index 00000000000..92ab5a88eb6 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitReentryPartSecond.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2001, 2026, 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 + * @bug 4413752 8262881 + * @summary Test SuspendThread with ObjectMonitor wait. + * @requires vm.jvmti + * @library /test/lib + * @compile SuspendWithObjectMonitorWaitReentryPartSecond.java + * @run main/othervm/native -agentlib:SuspendWithObjectMonitorWait SuspendWithObjectMonitorWaitReentryPartSecond 3 + */ + +import java.io.PrintStream; + +// +// SuspendWithObjectMonitorWaitReentryPartSecond algorithm: +// +// main waiter resumer +// =================== ====================== =================== +// launch waiter +// waiter running +// launch resumer enter threadLock +// while !READY_TO_NOTIFY resumer running +// : threadLock.wait(1) wait for notify +// enter threadLock : : +// set READY_TO_NOTIFY : +// threadLock.notify wait finishes : +// : delay 200ms reenter blocks : +// suspend waiter : +// : : +// : : : +// notify resumer : wait finishes +// delay 1-second : : +// exit threadLock : : +// join resumer : enter threadLock +// : resume waiter +// : : exit threadLock +// : reenter threadLock : +// : resumer exits +// join waiter : +// waiter exits +// +// Note: The sleep(1-second) in main along with the delayed exit +// of threadLock in main forces the resumer thread to reach +// "enter threadLock" and block. This difference from the default scenario +// forces the resumer thread to be contending for threadLock +// while the waiter thread is in the threadLock.wait(1) tight +// loop increasing stress on the monitor sub-system. +// +// Note: sleep(200ms) here while holding the threadLock to allow the +// waiter thread's timed wait to finish before we attempt to +// suspend the waiter thread. +// + +public class SuspendWithObjectMonitorWaitReentryPartSecond extends SuspendWithObjectMonitorWaitBase { + + @Override + public int run(int timeMax, PrintStream out) { + return doWork3(timeMax, out); + } + + // Suspend on the re-entry path of wait. + public int doWork3(int timeMax, PrintStream out) { + SuspendWithObjectMonitorWaitWorker waiter; // waiter thread + SuspendWithObjectMonitorWaitWorker resumer; // resumer thread + + System.out.println("Test 3: About to execute for " + timeMax + " seconds."); + + long start_time = System.currentTimeMillis(); + while (System.currentTimeMillis() < start_time + (timeMax * 1000)) { + count++; + testState = TS_INIT; // starting the test loop + + // launch the waiter thread + waiter = launchWaiter(100); + + // launch the resumer thread + resumer = launchResumer(waiter); + + checkTestState(TS_RESUMER_RUNNING); + + // The waiter thread was synchronized on threadLock before it + // set TS_WAITER_RUNNING and notified barrierLaunch above so + // we cannot enter threadLock until the waiter thread calls + // threadLock.wait(). + synchronized (threadLock) { + // notify waiter thread so it can try to reenter threadLock + testState = TS_READY_TO_NOTIFY; + threadLock.notify(); + + try { + Thread.sleep(200); + } catch (Exception e) {} + + // wait for the waiter thread to block + logDebug("before contended enter wait"); + int retCode = wait4ContendedEnter(waiter); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI GetThreadState: " + + "retCode=" + retCode); + } + logDebug("done contended enter wait"); + + checkTestState(TS_READY_TO_NOTIFY); + testState = TS_CALL_SUSPEND; + logDebug("before suspend thread"); + retCode = suspendThread(waiter); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI SuspendThread: " + + "retCode=" + retCode); + } + logDebug("suspended thread"); + + // + // At this point, all of the child threads are running + // and we can get to meat of the test: + // + // - suspended threadLock waiter (trying to reenter) + // - a blocked threadLock enter in the resumer thread while the + // threadLock is held by the main thread. + // - resumption of the waiter thread + // - a threadLock enter in the freshly resumed waiter thread + // + barrierResumerNotify(); + try { + // Delay for 1-second while holding the threadLock to force the + // resumer thread to block on entering the threadLock. + Thread.sleep(1000); + } catch (Exception e) {} + } + + shutDown(waiter ,resumer); + checkTestState(TS_WAITER_DONE); + } + + System.out.println("Executed " + count + " loops in " + timeMax + + " seconds."); + + return 0; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitWorker.java b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitWorker.java new file mode 100644 index 00000000000..2bf41168d5a --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/SuspendWithObjectMonitorWaitWorker.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2001, 2026, 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. + */ + +public class SuspendWithObjectMonitorWaitWorker extends Thread { + private SuspendWithObjectMonitorWaitWorker target; // target for resume operation + private final long waitTimeout; + + public SuspendWithObjectMonitorWaitWorker(String name, long waitTimeout) { + super(name); + this.waitTimeout = waitTimeout; + } + + public SuspendWithObjectMonitorWaitWorker(String name, SuspendWithObjectMonitorWaitWorker target) { + super(name); + this.target = target; + this.waitTimeout = 0; + } + + native static int resumeThread(SuspendWithObjectMonitorWaitWorker thr); + + public void run() { + SuspendWithObjectMonitorWaitBase.logDebug("thread running"); + + // + // Launch the waiter thread: + // - grab the threadLock + // - threadLock.wait() + // - releases threadLock + // + if (getName().equals("waiter")) { + // grab threadLock before we tell main we are running + SuspendWithObjectMonitorWaitBase.logDebug("before enter threadLock"); + synchronized(SuspendWithObjectMonitorWaitBase.threadLock) { + SuspendWithObjectMonitorWaitBase.logDebug("enter threadLock"); + + SuspendWithObjectMonitorWaitBase.checkTestState(SuspendWithObjectMonitorWaitBase.TS_INIT); + + synchronized(SuspendWithObjectMonitorWaitBase.barrierLaunch) { + // tell main we are running + SuspendWithObjectMonitorWaitBase.testState = SuspendWithObjectMonitorWaitBase.TS_WAITER_RUNNING; + SuspendWithObjectMonitorWaitBase.barrierLaunch.notify(); + } + + SuspendWithObjectMonitorWaitBase.logDebug("before wait"); + + // TS_READY_TO_NOTIFY is set after the main thread has + // entered threadLock so a spurious wakeup can't get the + // waiter thread out of this threadLock.wait(0) call: + while (SuspendWithObjectMonitorWaitBase.testState <= SuspendWithObjectMonitorWaitBase.TS_READY_TO_NOTIFY) { + try { + SuspendWithObjectMonitorWaitBase.threadLock.wait(waitTimeout); + } catch (InterruptedException ex) { + } + } + + SuspendWithObjectMonitorWaitBase.logDebug("after wait"); + + SuspendWithObjectMonitorWaitBase.checkTestState(SuspendWithObjectMonitorWaitBase.TS_CALL_RESUME); + SuspendWithObjectMonitorWaitBase.testState = SuspendWithObjectMonitorWaitBase.TS_WAITER_DONE; + + SuspendWithObjectMonitorWaitBase.logDebug("exit threadLock"); + } + } + // + // Launch the resumer thread: + // - tries to grab the threadLock (should not block with doWork1!) + // - grabs threadLock + // - resumes the waiter thread + // - releases threadLock + // + else if (getName().equals("resumer")) { + synchronized(SuspendWithObjectMonitorWaitBase.barrierResumer) { + synchronized(SuspendWithObjectMonitorWaitBase.barrierLaunch) { + // tell main we are running + SuspendWithObjectMonitorWaitBase.testState = SuspendWithObjectMonitorWaitBase.TS_RESUMER_RUNNING; + SuspendWithObjectMonitorWaitBase.barrierLaunch.notify(); + } + SuspendWithObjectMonitorWaitBase.logDebug("thread waiting"); + while (SuspendWithObjectMonitorWaitBase.testState != SuspendWithObjectMonitorWaitBase.TS_READY_TO_RESUME) { + try { + // wait for main to tell us when to continue + SuspendWithObjectMonitorWaitBase.barrierResumer.wait(0); + } catch (InterruptedException ex) { + } + } + } + + SuspendWithObjectMonitorWaitBase.logDebug("before enter threadLock"); + synchronized(SuspendWithObjectMonitorWaitBase.threadLock) { + SuspendWithObjectMonitorWaitBase.logDebug("enter threadLock"); + + SuspendWithObjectMonitorWaitBase.checkTestState(SuspendWithObjectMonitorWaitBase.TS_READY_TO_RESUME); + SuspendWithObjectMonitorWaitBase.testState = SuspendWithObjectMonitorWaitBase.TS_CALL_RESUME; + + // resume the waiter thread so waiter.join() can work + SuspendWithObjectMonitorWaitBase.logDebug("before resume thread"); + int retCode = resumeThread(target); + if (retCode != 0) { + throw new RuntimeException("error in JVMTI ResumeThread: " + + "retCode=" + retCode); + } + SuspendWithObjectMonitorWaitBase.logDebug("resumed thread"); + + SuspendWithObjectMonitorWaitBase.logDebug("exit threadLock"); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/libSuspendWithObjectMonitorWait.cpp b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/libSuspendWithObjectMonitorWait.cpp index 3706cba76dc..bea70c4925f 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/libSuspendWithObjectMonitorWait.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/SuspendWithObjectMonitorWait/libSuspendWithObjectMonitorWait.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -36,7 +36,7 @@ static jvmtiEnv* jvmti = nullptr; } while (0) JNIEXPORT int JNICALL -Java_SuspendWithObjectMonitorWait_suspendThread(JNIEnv *jni, jclass cls, jthread thr) { +Java_SuspendWithObjectMonitorWaitBase_suspendThread(JNIEnv *jni, jclass cls, jthread thr) { return jvmti->SuspendThread(thr); } @@ -46,7 +46,7 @@ Java_SuspendWithObjectMonitorWaitWorker_resumeThread(JNIEnv *jni, jclass cls, jt } JNIEXPORT jint JNICALL -Java_SuspendWithObjectMonitorWait_wait4ContendedEnter(JNIEnv *jni, jclass cls, jthread thr) { +Java_SuspendWithObjectMonitorWaitBase_wait4ContendedEnter(JNIEnv *jni, jclass cls, jthread thr) { jvmtiError err; jint thread_state; do {