Skip to content

Commit 211d6b6

Browse files
committed
v1
1 parent d02ac57 commit 211d6b6

3 files changed

Lines changed: 196 additions & 30 deletions

File tree

src/hotspot/share/runtime/objectMonitor.cpp

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -327,15 +327,16 @@ void ObjectMonitor::set_object_strong() {
327327
}
328328
}
329329

330-
void ObjectMonitor::ExitOnSuspend::operator()(JavaThread* current) {
331-
if (current->is_suspended()) {
330+
ObjectMonitor::ExitOnSuspend::~ExitOnSuspend() {
331+
if (_current->is_suspended()) {
332332
_om->_recursions = 0;
333333
_om->clear_successor();
334334
// Don't need a full fence after clearing successor here because of the call to exit().
335-
_om->exit(current, false /* not_suspended */);
335+
_om->exit(_current, false /* not_suspended */);
336336
_om_exited = true;
337-
338-
current->set_current_pending_monitor(_om);
337+
_current->set_current_pending_monitor(_om);
338+
// Process suspend request now
339+
SafepointMechanism::process_if_requested(_current, true /*allow_suspend*/, false /*check_async_exception*/);
339340
}
340341
}
341342

@@ -590,9 +591,9 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito
590591
assert(current->thread_state() == _thread_in_vm, "invariant");
591592

592593
for (;;) {
593-
ExitOnSuspend eos(this);
594+
bool om_exited = false;
594595
{
595-
ThreadBlockInVMPreprocess<ExitOnSuspend> tbivs(current, eos, true /* allow_suspend */);
596+
ExitOnSuspend eos(current, this, om_exited);
596597
enter_internal(current);
597598
current->set_current_pending_monitor(nullptr);
598599
// We can go to a safepoint at the end of this block. If we
@@ -604,7 +605,7 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito
604605
// and set the OM as pending, the thread will not be reported as
605606
// having "-locked" the monitor.
606607
}
607-
if (!eos.exited()) {
608+
if (!om_exited) {
608609
// ExitOnSuspend did not exit the OM
609610
assert(has_owner(current), "invariant");
610611
break;
@@ -938,7 +939,7 @@ const char* ObjectMonitor::is_busy_to_string(stringStream* ss) {
938939
}
939940

940941
void ObjectMonitor::enter_internal(JavaThread* current) {
941-
assert(current->thread_state() == _thread_blocked, "invariant");
942+
assert(current->thread_state() == _thread_in_vm, "invariant");
942943

943944
// Try the lock - TATAS
944945
if (try_lock(current) == TryLockResult::Success) {
@@ -1014,16 +1015,19 @@ void ObjectMonitor::enter_internal(JavaThread* current) {
10141015
}
10151016
assert(!has_owner(current), "invariant");
10161017

1017-
// park self
1018-
if (do_timed_parked) {
1019-
current->_ParkEvent->park(recheck_interval);
1020-
// Increase the recheck_interval, but clamp the value.
1021-
recheck_interval *= 8;
1022-
if (recheck_interval > MAX_RECHECK_INTERVAL) {
1023-
recheck_interval = MAX_RECHECK_INTERVAL;
1018+
{
1019+
ThreadBlockInVM tbivm(current);
1020+
// park self
1021+
if (do_timed_parked) {
1022+
current->_ParkEvent->park(recheck_interval);
1023+
// Increase the recheck_interval, but clamp the value.
1024+
recheck_interval *= 8;
1025+
if (recheck_interval > MAX_RECHECK_INTERVAL) {
1026+
recheck_interval = MAX_RECHECK_INTERVAL;
1027+
}
1028+
} else {
1029+
current->_ParkEvent->park();
10241030
}
1025-
} else {
1026-
current->_ParkEvent->park();
10271031
}
10281032

10291033
if (try_lock(current) == TryLockResult::Success) {
@@ -1094,7 +1098,7 @@ void ObjectMonitor::enter_internal(JavaThread* current) {
10941098

10951099
void ObjectMonitor::reenter_internal(JavaThread* current, ObjectWaiter* currentNode) {
10961100
assert(current != nullptr, "invariant");
1097-
assert(current->thread_state() == _thread_blocked, "invariant");
1101+
assert(current->thread_state() == _thread_in_vm, "invariant");
10981102
assert(currentNode != nullptr, "invariant");
10991103
assert(currentNode->_thread == current, "invariant");
11001104
assert(_waiters > 0, "invariant");
@@ -1132,6 +1136,7 @@ void ObjectMonitor::reenter_internal(JavaThread* current, ObjectWaiter* currentN
11321136
}
11331137

11341138
{
1139+
ThreadBlockInVM tbivm(current);
11351140
OSThreadContendState osts(current->osthread());
11361141
if (do_timed_parked) {
11371142
current->_ParkEvent->park(recheck_interval);
@@ -1644,6 +1649,7 @@ void ObjectMonitor::exit_epilog(JavaThread* current, ObjectWaiter* Wakee) {
16441649
Trigger = t->_ParkEvent;
16451650
set_successor(t);
16461651
} else {
1652+
assert_not_at_safepoint();
16471653
vthread = Wakee->vthread();
16481654
assert(vthread != nullptr, "");
16491655
Trigger = ObjectMonitor::vthread_unparker_ParkEvent();
@@ -1962,9 +1968,9 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
19621968
// This means the thread has been un-parked and added to the entry_list
19631969
// in notify_internal, i.e. notified while waiting.
19641970
guarantee(v == ObjectWaiter::TS_ENTER, "invariant");
1965-
ExitOnSuspend eos(this);
1971+
bool om_exited = false;
19661972
{
1967-
ThreadBlockInVMPreprocess<ExitOnSuspend> tbivs(current, eos, true /* allow_suspend */);
1973+
ExitOnSuspend eos(current, this, om_exited);
19681974
reenter_internal(current, &node);
19691975
// We can go to a safepoint at the end of this block. If we
19701976
// do a thread dump during that safepoint, then this thread will show
@@ -1975,7 +1981,7 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
19751981
// and set the OM as pending, the thread will not be reported as
19761982
// having "-locked" the monitor.
19771983
}
1978-
if (eos.exited()) {
1984+
if (om_exited) {
19791985
// ExitOnSuspend exit the OM
19801986
assert(!has_owner(current), "invariant");
19811987
guarantee(node.TState == ObjectWaiter::TS_RUN, "invariant");
@@ -2396,6 +2402,8 @@ bool ObjectMonitor::short_fixed_spin(JavaThread* current, int spin_count, bool a
23962402

23972403
// Spinning: Fixed frequency (100%), vary duration
23982404
bool ObjectMonitor::try_spin(JavaThread* current) {
2405+
assert(current->thread_state() == _thread_in_vm || current->thread_state() == _thread_in_Java, "");
2406+
bool from_java = current->thread_state() == _thread_in_Java;
23992407

24002408
// Dumb, brutal spin. Good for comparative measurements against adaptive spinning.
24012409
int knob_fixed_spin = Knob_FixedSpin; // 0 (don't spin: default), 2000 good test
@@ -2426,6 +2434,13 @@ bool ObjectMonitor::try_spin(JavaThread* current) {
24262434
int ctr = _SpinDuration;
24272435
if (ctr <= 0) return false;
24282436

2437+
// Guarantee that we at least poll once if main
2438+
// spinning loop is executed.
2439+
if (SafepointMechanism::local_poll_armed(current)) {
2440+
if (from_java) return false;
2441+
ThreadBlockInVM tbivm(current);
2442+
}
2443+
24292444
// We're good to spin ... spin ingress.
24302445
// CONSIDER: use Prefetch::write() to avoid RTS->RTO upgrades
24312446
// when preparing to LD...CAS _owner, etc and the CAS is likely
@@ -2452,11 +2467,9 @@ bool ObjectMonitor::try_spin(JavaThread* current) {
24522467
// This is in keeping with the "no loitering in runtime" rule.
24532468
// We periodically check to see if there's a safepoint pending.
24542469
if ((ctr & 0xFF) == 0) {
2455-
// Can't call SafepointMechanism::should_process() since that
2456-
// might update the poll values and we could be in a thread_blocked
2457-
// state here which is not allowed so just check the poll.
24582470
if (SafepointMechanism::local_poll_armed(current)) {
2459-
break;
2471+
if (from_java) break;
2472+
ThreadBlockInVM tbivm(current);
24602473
}
24612474
SpinPause();
24622475
}

src/hotspot/share/runtime/objectMonitor.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -353,12 +353,13 @@ class ObjectMonitor : public CHeapObj<mtObjectMonitor> {
353353

354354
class ExitOnSuspend {
355355
protected:
356+
JavaThread* _current;
356357
ObjectMonitor* _om;
357-
bool _om_exited;
358+
bool& _om_exited;
358359
public:
359-
ExitOnSuspend(ObjectMonitor* om) : _om(om), _om_exited(false) {}
360-
void operator()(JavaThread* current);
361-
bool exited() { return _om_exited; }
360+
ExitOnSuspend(JavaThread* current, ObjectMonitor* om, bool& om_exited)
361+
: _current(current), _om(om), _om_exited(om_exited) { assert(!_om_exited, ""); }
362+
~ExitOnSuspend();
362363
};
363364

364365
bool enter_is_async_deflating();
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/**
25+
* @test
26+
* @bug 8373944
27+
* @summary Suspend thread while it's trying to acquire a monitor when unmounted vthreads are in the queue
28+
* @requires vm.continuations
29+
* @requires vm.jvmti
30+
* @library /test/lib /test/hotspot/jtreg/testlibrary
31+
* @run main/othervm/native SuspendResume3
32+
*/
33+
34+
import java.time.Instant;
35+
import java.util.concurrent.CountDownLatch;
36+
import java.util.concurrent.Phaser;
37+
38+
import jvmti.JVMTIUtils;
39+
40+
public class SuspendResume3 {
41+
int iterations;
42+
int dummyCounter;
43+
Object lock = new Object();
44+
Phaser allSync = new Phaser(3);
45+
46+
SuspendResume3 (int iterations) {
47+
this.iterations = iterations;
48+
}
49+
50+
void worker1(Phaser sync1, Phaser sync2) {
51+
for (int i = 0; i < iterations; i++) {
52+
synchronized (lock) {
53+
sync1.arriveAndAwaitAdvance();
54+
sync2.arriveAndAwaitAdvance();
55+
}
56+
allSync.arriveAndAwaitAdvance();
57+
}
58+
};
59+
60+
void worker2(Phaser sync1, Phaser sync2) {
61+
for (int i = 0; i < iterations; i++) {
62+
sync1.arriveAndAwaitAdvance();
63+
synchronized (lock) {
64+
sync2.arriveAndAwaitAdvance();
65+
}
66+
allSync.arriveAndAwaitAdvance();
67+
}
68+
};
69+
70+
void vthreadWorker(CountDownLatch started) {
71+
started.countDown();
72+
synchronized (lock) {
73+
dummyCounter++;
74+
}
75+
}
76+
77+
private void runTest() throws Exception {
78+
final Phaser w1Sync1 = new Phaser(2);
79+
final Phaser w1Sync2 = new Phaser(2);
80+
Thread worker1 = Thread.ofPlatform().start(() -> worker1(w1Sync1, w1Sync2));
81+
82+
final Phaser w2Sync1 = new Phaser(2);
83+
final Phaser w2Sync2 = new Phaser(2);
84+
Thread worker2 = Thread.ofPlatform().start(() -> worker2(w2Sync1, w2Sync2));
85+
86+
for (int i = 0; i < iterations; i++) {
87+
// Wait until worker1 acquires monitor
88+
w1Sync1.arriveAndAwaitAdvance();
89+
// Let worker2 block on monitor
90+
w2Sync1.arriveAndAwaitAdvance();
91+
await(worker2, Thread.State.BLOCKED);
92+
93+
// Suspend worker2
94+
JVMTIUtils.suspendThread(worker2);
95+
96+
// Add umounted vthread to _entry_list
97+
var started = new CountDownLatch(1);
98+
Thread vthread = Thread.ofVirtual().start(() -> vthreadWorker(started));
99+
started.await();
100+
await(vthread, Thread.State.BLOCKED);
101+
102+
// Now let worker1 release the monitor picking worker2
103+
// as successor. Since worker2 is suspended, it will wake
104+
// up, acquire the monitor and release it, unparking the
105+
// unmounted thread as next successor.
106+
w1Sync2.arriveAndAwaitAdvance();
107+
108+
// Force safepoint
109+
System.gc();
110+
111+
// Let vthread terminate
112+
vthread.join();
113+
114+
// Resume worker2
115+
JVMTIUtils.resumeThread(worker2);
116+
w2Sync2.arriveAndAwaitAdvance();
117+
118+
if ((i % 10) == 0) {
119+
System.out.println(Instant.now() + " => " + i + " of " + iterations);
120+
}
121+
allSync.arriveAndAwaitAdvance();
122+
}
123+
124+
worker1.join();
125+
worker2.join();
126+
}
127+
128+
public static void main(String[] args) throws Exception {
129+
int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 100;
130+
131+
SuspendResume3 obj = new SuspendResume3(iterations);
132+
obj.runTest();
133+
}
134+
135+
/**
136+
* Waits for the given thread to reach a given state.
137+
*/
138+
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
139+
Thread.State state = thread.getState();
140+
while (state != expectedState) {
141+
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
142+
Thread.sleep(10);
143+
state = thread.getState();
144+
}
145+
}
146+
147+
private static void assertTrue(boolean condition, String msg) {
148+
if (!condition) {
149+
throw new RuntimeException(msg);
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)