8366659: ObjectMonitor::wait() liveness problem with a suspension request

Co-authored-by: Patricio Chilano Mateo <pchilanomate@openjdk.org>
Co-authored-by: Daniel D. Daugherty <dcubed@openjdk.org>
Reviewed-by: dcubed, sspitsyn, dholmes, pchilanomate
This commit is contained in:
Anton Artemov 2026-02-02 08:20:00 +00:00
parent 766e03b151
commit 1f3fd3da1d
9 changed files with 952 additions and 486 deletions

View File

@ -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<ClearSuccOnSuspend> 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<ClearSuccOnSuspend> 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<ExitOnSuspend> 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);

View File

@ -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<mtThread> {
public:
enum TStates : uint8_t { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER };
@ -72,7 +71,7 @@ class ObjectWaiter : public CHeapObj<mtThread> {
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<mtObjectMonitor> {
// returns false and throws IllegalMonitorStateException (IMSE).
bool check_owner(TRAPS);
private:
class ExitOnSuspend {
protected:
ObjectMonitor* _om;
@ -362,23 +360,16 @@ class ObjectMonitor : public CHeapObj<mtObjectMonitor> {
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);

View File

@ -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
// <launch returns> waiter running
// launch resumer enter threadLock
// <launch returns> threadLock.wait() resumer running
// enter threadLock : wait for notify
// threadLock.notify wait finishes :
// : reenter blocks :
// suspend waiter <suspended> :
// exit threadLock : :
// <ready to test> : :
// : : :
// notify resumer : wait finishes
// join resumer : enter threadLock
// : <resumed> resume waiter
// : : exit threadLock
// : reenter threadLock :
// <join returns> : resumer exits
// join waiter :
// <join returns> 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");
}
}
}
}

View File

@ -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);
}
}

View File

@ -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
// <launch returns> waiter running
// launch resumer enter threadLock
// <launch returns> threadLock.wait() resumer running
// enter threadLock : wait for notify
// threadLock.notify wait finishes :
// : reenter blocks :
// suspend waiter <suspended> :
// exit threadLock : :
// <ready to test> : :
// : : :
// notify resumer : wait finishes
// join resumer : enter threadLock
// : <resumed> resume waiter
// : : exit threadLock
// : reenter threadLock :
// <join returns> : resumer exits
// join waiter :
// <join returns> 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;
}
}

View File

@ -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
// <launch returns> waiter running
// launch resumer enter threadLock
// <launch returns> threadLock.wait() resumer running
// enter threadLock : wait for notify
// threadLock.notify wait finishes :
// : reenter blocks :
// suspend waiter <suspended> :
// <ready to test> : :
// : : :
// notify resumer : wait finishes
// delay 1-second : :
// exit threadLock : :
// join resumer : enter threadLock
// : <resumed> resume waiter
// : : exit threadLock
// : reenter threadLock :
// <join returns> : resumer exits
// join waiter :
// <join returns> 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;
}
}

View File

@ -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
// <launch returns> waiter running
// launch resumer enter threadLock
// <launch returns> 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 <suspended> :
// <ready to test> : :
// : : :
// notify resumer : wait finishes
// delay 1-second : :
// exit threadLock : :
// join resumer : enter threadLock
// : <resumed> resume waiter
// : : exit threadLock
// : reenter threadLock :
// <join returns> : resumer exits
// join waiter :
// <join returns> 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;
}
}

View File

@ -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");
}
}
}
}

View File

@ -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 {