Skip to content

Commit 246d526

Browse files
pchilanoDavid Holmes
authored andcommitted
8373944: ObjectMonitor::ExitOnSuspend can call java_lang_VirtualThread::set_onWaitingList() while in safepoint
Reviewed-by: dholmes Backport-of: 4b99aef
1 parent f262923 commit 246d526

2 files changed

Lines changed: 185 additions & 2 deletions

File tree

src/hotspot/share/runtime/objectMonitor.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,13 +328,18 @@ void ObjectMonitor::set_object_strong() {
328328

329329
void ObjectMonitor::ExitOnSuspend::operator()(JavaThread* current) {
330330
if (current->is_suspended()) {
331+
// There could be an ongoing safepoint/handshake operation.
332+
// Process them, except suspend requests, before exiting the
333+
// monitor, as this may involve touching oops if the successor
334+
// is a virtual thread. Before processing pending operations,
335+
// set the monitor as pending again.
336+
current->set_current_pending_monitor(_om);
337+
SafepointMechanism::process_if_requested(current, false /*allow_suspend*/, false /*check_async_exception*/);
331338
_om->_recursions = 0;
332339
_om->clear_successor();
333340
// Don't need a full fence after clearing successor here because of the call to exit().
334341
_om->exit(current, false /* not_suspended */);
335342
_om_exited = true;
336-
337-
current->set_current_pending_monitor(_om);
338343
}
339344
}
340345

@@ -1660,6 +1665,7 @@ void ObjectMonitor::exit_epilog(JavaThread* current, ObjectWaiter* Wakee) {
16601665
Trigger = t->_ParkEvent;
16611666
set_successor(t);
16621667
} else {
1668+
assert_not_at_safepoint();
16631669
vthread = Wakee->vthread();
16641670
assert(vthread != nullptr, "");
16651671
Trigger = ObjectMonitor::vthread_unparker_ParkEvent();
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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 java.lang.management.LockInfo;
39+
import java.lang.management.ManagementFactory;
40+
import java.lang.management.ThreadInfo;
41+
import java.lang.management.ThreadMXBean;
42+
43+
import jvmti.JVMTIUtils;
44+
45+
public class SuspendResume3 {
46+
int iterations;
47+
int dummyCounter;
48+
Object lock = new Object();
49+
Phaser allSync = new Phaser(3);
50+
51+
SuspendResume3 (int iterations) {
52+
this.iterations = iterations;
53+
}
54+
55+
void worker1(Phaser sync1, Phaser sync2) {
56+
for (int i = 0; i < iterations; i++) {
57+
synchronized (lock) {
58+
sync1.arriveAndAwaitAdvance();
59+
sync2.arriveAndAwaitAdvance();
60+
}
61+
allSync.arriveAndAwaitAdvance();
62+
}
63+
};
64+
65+
void worker2(Phaser sync1, Phaser sync2) {
66+
for (int i = 0; i < iterations; i++) {
67+
sync1.arriveAndAwaitAdvance();
68+
synchronized (lock) {
69+
sync2.arriveAndAwaitAdvance();
70+
}
71+
allSync.arriveAndAwaitAdvance();
72+
}
73+
};
74+
75+
void vthreadWorker(CountDownLatch started) {
76+
started.countDown();
77+
synchronized (lock) {
78+
dummyCounter++;
79+
}
80+
}
81+
82+
private void runTest() throws Exception {
83+
final Phaser w1Sync1 = new Phaser(2);
84+
final Phaser w1Sync2 = new Phaser(2);
85+
Thread worker1 = Thread.ofPlatform().start(() -> worker1(w1Sync1, w1Sync2));
86+
87+
final Phaser w2Sync1 = new Phaser(2);
88+
final Phaser w2Sync2 = new Phaser(2);
89+
Thread worker2 = Thread.ofPlatform().start(() -> worker2(w2Sync1, w2Sync2));
90+
91+
for (int i = 0; i < iterations; i++) {
92+
// Wait until worker1 acquires monitor
93+
w1Sync1.arriveAndAwaitAdvance();
94+
// Let worker2 block on monitor
95+
w2Sync1.arriveAndAwaitAdvance();
96+
// Wait until worker2 blocks trying to acquire lock. We can't just check
97+
// for a BLOCKED state because method arriveAndAwaitAdvance might involve
98+
// extra class loading/initialization where worker2 could be seen as BLOCKED
99+
// and thus be suspended below. If the main thread then tries to access those
100+
// same classes before resuming worker2 the test would deadlock.
101+
awaitBlockedOnLock(worker2);
102+
103+
// Suspend worker2
104+
JVMTIUtils.suspendThread(worker2);
105+
106+
// Add umounted vthread to _entry_list
107+
var started = new CountDownLatch(1);
108+
Thread vthread = Thread.ofVirtual().start(() -> vthreadWorker(started));
109+
started.await();
110+
await(vthread, Thread.State.BLOCKED);
111+
112+
// Now let worker1 release the monitor picking worker2
113+
// as successor. Since worker2 is suspended, it will wake
114+
// up, acquire the monitor and release it, unparking the
115+
// unmounted thread as next successor.
116+
w1Sync2.arriveAndAwaitAdvance();
117+
118+
// Force safepoint
119+
System.gc();
120+
121+
// Let vthread terminate
122+
vthread.join();
123+
124+
// Resume worker2
125+
JVMTIUtils.resumeThread(worker2);
126+
w2Sync2.arriveAndAwaitAdvance();
127+
128+
if ((i % 10) == 0) {
129+
System.out.println(Instant.now() + " => " + i + " of " + iterations);
130+
}
131+
allSync.arriveAndAwaitAdvance();
132+
}
133+
134+
worker1.join();
135+
worker2.join();
136+
}
137+
138+
public static void main(String[] args) throws Exception {
139+
int iterations = (args.length > 0) ? Integer.parseInt(args[0]) : 100;
140+
141+
SuspendResume3 obj = new SuspendResume3(iterations);
142+
obj.runTest();
143+
}
144+
145+
/**
146+
* Waits for the given thread to reach a given state.
147+
*/
148+
private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
149+
Thread.State state = thread.getState();
150+
while (state != expectedState) {
151+
assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
152+
Thread.sleep(10);
153+
state = thread.getState();
154+
}
155+
}
156+
157+
/**
158+
* Waits for the given thread to block trying to acquire lock's monitor.
159+
*/
160+
private void awaitBlockedOnLock(Thread thread) throws InterruptedException {
161+
while (true) {
162+
ThreadInfo threadInfo = ManagementFactory.getThreadMXBean().getThreadInfo(thread.threadId());
163+
assertTrue(threadInfo != null, "getThreadInfo() failed");
164+
LockInfo lockInfo = threadInfo.getLockInfo();
165+
if (lockInfo != null && lockInfo.getIdentityHashCode() == System.identityHashCode(lock)) {
166+
break;
167+
}
168+
Thread.sleep(10);
169+
}
170+
}
171+
172+
private static void assertTrue(boolean condition, String msg) {
173+
if (!condition) {
174+
throw new RuntimeException(msg);
175+
}
176+
}
177+
}

0 commit comments

Comments
 (0)