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 id=default
26 * @summary Test virtual thread with monitor enter/exit
27 * @modules java.base/java.lang:+open
28 * @library /test/lib
29 * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
30 */
31
32 import java.time.Duration;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.ThreadFactory;
37 import java.util.concurrent.ThreadLocalRandom;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.atomic.AtomicBoolean;
41 import java.util.concurrent.locks.LockSupport;
42 import java.util.stream.IntStream;
43 import java.util.stream.Stream;
44
45 import jdk.test.lib.thread.VThreadPinner;
46 import jdk.test.lib.thread.VThreadRunner;
47 import jdk.test.lib.thread.VThreadScheduler;
48 import org.junit.jupiter.api.Test;
49 import org.junit.jupiter.api.BeforeAll;
50 import org.junit.jupiter.api.RepeatedTest;
51 import org.junit.jupiter.params.ParameterizedTest;
52 import org.junit.jupiter.params.provider.Arguments;
53 import org.junit.jupiter.params.provider.ValueSource;
54 import org.junit.jupiter.params.provider.MethodSource;
55 import org.junit.jupiter.api.condition.*;
56 import static org.junit.jupiter.api.Assertions.*;
57 import static org.junit.jupiter.api.Assumptions.*;
58
59 class MonitorEnterExit {
60 static final int MAX_ENTER_DEPTH = 256;
61
62 @BeforeAll
63 static void setup() {
64 // need >=2 carriers for testing pinning when main thread is a virtual thread
65 if (Thread.currentThread().isVirtual()) {
66 VThreadRunner.ensureParallelism(2);
67 }
68 }
69
70 /**
71 * Test monitor enter with no contention.
72 */
73 @Test
74 void testEnterNoContention() throws Exception {
75 var lock = new Object();
76 VThreadRunner.run(() -> {
77 synchronized (lock) {
78 assertTrue(Thread.holdsLock(lock));
79 }
80 assertFalse(Thread.holdsLock(lock));
81 });
82 }
83
84 /**
85 * Test monitor enter with contention, monitor is held by platform thread.
86 */
87 @Test
133 */
134 @Test
135 void testReenter() throws Exception {
136 var lock = new Object();
137 VThreadRunner.run(() -> {
138 testReenter(lock, 0);
139 assertFalse(Thread.holdsLock(lock));
140 });
141 }
142
143 private void testReenter(Object lock, int depth) {
144 if (depth < MAX_ENTER_DEPTH) {
145 synchronized (lock) {
146 assertTrue(Thread.holdsLock(lock));
147 testReenter(lock, depth + 1);
148 assertTrue(Thread.holdsLock(lock));
149 }
150 }
151 }
152
153 /**
154 * Test monitor enter when pinned.
155 */
156 @Test
157 void testEnterWhenPinned() throws Exception {
158 var lock = new Object();
159 VThreadPinner.runPinned(() -> {
160 synchronized (lock) {
161 assertTrue(Thread.holdsLock(lock));
162 }
163 assertFalse(Thread.holdsLock(lock));
164 });
165 }
166
167 /**
168 * Test monitor reenter when pinned.
169 */
170 @Test
171 void testReenterWhenPinned() throws Exception {
172 VThreadRunner.run(() -> {
180 assertTrue(Thread.holdsLock(lock));
181 });
182 }
183 assertFalse(Thread.holdsLock(lock));
184 });
185 }
186
187 /**
188 * Test contended monitor enter when pinned. Monitor is held by platform thread.
189 */
190 @Test
191 void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
192 testEnterWithContentionWhenPinned();
193 }
194
195 /**
196 * Test contended monitor enter when pinned. Monitor is held by virtual thread.
197 */
198 @Test
199 void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
200 // need at least two carrier threads
201 int previousParallelism = VThreadRunner.ensureParallelism(2);
202 try {
203 VThreadRunner.run(this::testEnterWithContentionWhenPinned);
204 } finally {
205 VThreadRunner.setParallelism(previousParallelism);
206 }
207 }
208
209 /**
210 * Test contended monitor enter when pinned, monitor will be held by caller thread.
211 */
212 private void testEnterWithContentionWhenPinned() throws Exception {
213 var lock = new Object();
214 var started = new CountDownLatch(1);
215 var entered = new AtomicBoolean();
216 Thread vthread = Thread.ofVirtual().unstarted(() -> {
217 VThreadPinner.runPinned(() -> {
218 started.countDown();
219 synchronized (lock) {
220 entered.set(true);
221 }
222 });
223 });
224 synchronized (lock) {
225 // start thread and wait for it to block
226 vthread.start();
227 started.await();
228 await(vthread, Thread.State.BLOCKED);
229 assertFalse(entered.get());
230 }
231 vthread.join();
232
233 // check thread entered monitor
234 assertTrue(entered.get());
235 }
236
237 /**
238 * Returns a stream of elements that are ordered pairs of platform and virtual thread
239 * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
240 */
241 static Stream<Arguments> threadCounts() {
242 return IntStream.range(0, 17)
243 .filter(i -> i % 2 == 0)
244 .mapToObj(i -> i)
245 .flatMap(np -> IntStream.range(2, 33)
246 .filter(i -> i % 2 == 0)
247 .mapToObj(vp -> Arguments.of(np, vp)));
248 }
249
250 /**
251 * Test mutual exclusion of monitors with platform and virtual threads.
252 */
253 @ParameterizedTest
254 @MethodSource("threadCounts")
255 void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
256 class Counter {
|
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 id=default
26 * @summary Test virtual thread with monitor enter/exit
27 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
28 * @modules java.base/java.lang:+open jdk.management
29 * @library /test/lib
30 * @build LockingMode
31 * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
32 */
33
34 /*
35 * @test id=LM_LEGACY
36 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
37 * @modules java.base/java.lang:+open jdk.management
38 * @library /test/lib
39 * @build LockingMode
40 * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
41 */
42
43 /*
44 * @test id=LM_LIGHTWEIGHT
45 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
46 * @modules java.base/java.lang:+open jdk.management
47 * @library /test/lib
48 * @build LockingMode
49 * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
50 */
51
52 /*
53 * @test id=Xint-LM_LEGACY
54 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
55 * @modules java.base/java.lang:+open jdk.management
56 * @library /test/lib
57 * @build LockingMode
58 * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
59 */
60
61 /*
62 * @test id=Xint-LM_LIGHTWEIGHT
63 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
64 * @modules java.base/java.lang:+open jdk.management
65 * @library /test/lib
66 * @build LockingMode
67 * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
68 */
69
70 /*
71 * @test id=Xcomp-LM_LEGACY
72 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
73 * @modules java.base/java.lang:+open jdk.management
74 * @library /test/lib
75 * @build LockingMode
76 * @run junit/othervm -Xcomp -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
77 */
78
79 /*
80 * @test id=Xcomp-LM_LIGHTWEIGHT
81 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
82 * @modules java.base/java.lang:+open jdk.management
83 * @library /test/lib
84 * @build LockingMode
85 * @run junit/othervm -Xcomp -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
86 */
87
88 /*
89 * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY
90 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
91 * @modules java.base/java.lang:+open jdk.management
92 * @library /test/lib
93 * @build LockingMode
94 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
95 */
96
97 /*
98 * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT
99 * @modules java.base/java.lang:+open jdk.management
100 * @library /test/lib
101 * @build LockingMode
102 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
103 */
104
105 /*
106 * @test id=Xcomp-noTieredCompilation-LM_LEGACY
107 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
108 * @modules java.base/java.lang:+open jdk.management
109 * @library /test/lib
110 * @build LockingMode
111 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
112 */
113
114 /*
115 * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT
116 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
117 * @modules java.base/java.lang:+open jdk.management
118 * @library /test/lib
119 * @build LockingMode
120 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
121 */
122
123 import java.time.Duration;
124 import java.util.ArrayList;
125 import java.util.List;
126 import java.util.concurrent.CountDownLatch;
127 import java.util.concurrent.ThreadFactory;
128 import java.util.concurrent.ThreadLocalRandom;
129 import java.util.concurrent.Executors;
130 import java.util.concurrent.ExecutorService;
131 import java.util.concurrent.atomic.AtomicBoolean;
132 import java.util.concurrent.locks.LockSupport;
133 import java.util.stream.IntStream;
134 import java.util.stream.Stream;
135
136 import jdk.test.lib.thread.VThreadPinner;
137 import jdk.test.lib.thread.VThreadRunner;
138 import jdk.test.lib.thread.VThreadScheduler;
139 import org.junit.jupiter.api.Test;
140 import org.junit.jupiter.api.BeforeAll;
141 import org.junit.jupiter.api.RepeatedTest;
142 import org.junit.jupiter.api.condition.DisabledIf;
143 import org.junit.jupiter.params.ParameterizedTest;
144 import org.junit.jupiter.params.provider.Arguments;
145 import org.junit.jupiter.params.provider.ValueSource;
146 import org.junit.jupiter.params.provider.MethodSource;
147 import static org.junit.jupiter.api.Assertions.*;
148 import static org.junit.jupiter.api.Assumptions.*;
149
150 class MonitorEnterExit {
151 static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
152 static final int MAX_ENTER_DEPTH = 256;
153
154 @BeforeAll
155 static void setup() {
156 // need >=2 carriers for tests that pin
157 VThreadRunner.ensureParallelism(2);
158 }
159
160 /**
161 * Test monitor enter with no contention.
162 */
163 @Test
164 void testEnterNoContention() throws Exception {
165 var lock = new Object();
166 VThreadRunner.run(() -> {
167 synchronized (lock) {
168 assertTrue(Thread.holdsLock(lock));
169 }
170 assertFalse(Thread.holdsLock(lock));
171 });
172 }
173
174 /**
175 * Test monitor enter with contention, monitor is held by platform thread.
176 */
177 @Test
223 */
224 @Test
225 void testReenter() throws Exception {
226 var lock = new Object();
227 VThreadRunner.run(() -> {
228 testReenter(lock, 0);
229 assertFalse(Thread.holdsLock(lock));
230 });
231 }
232
233 private void testReenter(Object lock, int depth) {
234 if (depth < MAX_ENTER_DEPTH) {
235 synchronized (lock) {
236 assertTrue(Thread.holdsLock(lock));
237 testReenter(lock, depth + 1);
238 assertTrue(Thread.holdsLock(lock));
239 }
240 }
241 }
242
243 /**
244 * Test monitor reenter when there are other threads blocked trying to enter.
245 */
246 @Test
247 @DisabledIf("LockingMode#isLegacy")
248 void testReenterWithContention() throws Exception {
249 var lock = new Object();
250 VThreadRunner.run(() -> {
251 List<Thread> threads = new ArrayList<>();
252 testReenter(lock, 0, threads);
253
254 // wait for threads to terminate
255 for (Thread vthread : threads) {
256 vthread.join();
257 }
258 });
259 }
260
261 private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
262 if (depth < MAX_ENTER_DEPTH) {
263 synchronized (lock) {
264 assertTrue(Thread.holdsLock(lock));
265
266 // start platform or virtual thread that blocks waiting to enter
267 var started = new CountDownLatch(1);
268 ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
269 ? Thread.ofPlatform().factory()
270 : Thread.ofVirtual().factory();
271 var thread = factory.newThread(() -> {
272 started.countDown();
273 synchronized (lock) {
274 /* do nothing */
275 }
276 });
277 thread.start();
278
279 // wait for thread to start and block
280 started.await();
281 await(thread, Thread.State.BLOCKED);
282 threads.add(thread);
283
284 // test reenter
285 testReenter(lock, depth + 1, threads);
286 }
287 }
288 }
289
290 /**
291 * Test monitor enter when pinned.
292 */
293 @Test
294 void testEnterWhenPinned() throws Exception {
295 var lock = new Object();
296 VThreadPinner.runPinned(() -> {
297 synchronized (lock) {
298 assertTrue(Thread.holdsLock(lock));
299 }
300 assertFalse(Thread.holdsLock(lock));
301 });
302 }
303
304 /**
305 * Test monitor reenter when pinned.
306 */
307 @Test
308 void testReenterWhenPinned() throws Exception {
309 VThreadRunner.run(() -> {
317 assertTrue(Thread.holdsLock(lock));
318 });
319 }
320 assertFalse(Thread.holdsLock(lock));
321 });
322 }
323
324 /**
325 * Test contended monitor enter when pinned. Monitor is held by platform thread.
326 */
327 @Test
328 void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
329 testEnterWithContentionWhenPinned();
330 }
331
332 /**
333 * Test contended monitor enter when pinned. Monitor is held by virtual thread.
334 */
335 @Test
336 void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
337 VThreadRunner.run(this::testEnterWithContentionWhenPinned);
338 }
339
340 /**
341 * Test contended monitor enter when pinned, monitor will be held by caller thread.
342 */
343 private void testEnterWithContentionWhenPinned() throws Exception {
344 var lock = new Object();
345 var started = new CountDownLatch(1);
346 var entered = new AtomicBoolean();
347 Thread vthread = Thread.ofVirtual().unstarted(() -> {
348 VThreadPinner.runPinned(() -> {
349 started.countDown();
350 synchronized (lock) {
351 entered.set(true);
352 }
353 });
354 });
355 synchronized (lock) {
356 // start thread and wait for it to block
357 vthread.start();
358 started.await();
359 await(vthread, Thread.State.BLOCKED);
360 assertFalse(entered.get());
361 }
362 vthread.join();
363
364 // check thread entered monitor
365 assertTrue(entered.get());
366 }
367
368 /**
369 * Test that blocking waiting to enter a monitor releases the carrier.
370 */
371 @Test
372 @DisabledIf("LockingMode#isLegacy")
373 void testReleaseWhenBlocked() throws Exception {
374 assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
375 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
376 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
377
378 var lock = new Object();
379
380 // thread enters monitor
381 var started = new CountDownLatch(1);
382 var vthread1 = factory.newThread(() -> {
383 started.countDown();
384 synchronized (lock) {
385 }
386 });
387
388 try {
389 synchronized (lock) {
390 // start thread and wait for it to block
391 vthread1.start();
392 started.await();
393 await(vthread1, Thread.State.BLOCKED);
394
395 // carrier should be released, use it for another thread
396 var executed = new AtomicBoolean();
397 var vthread2 = factory.newThread(() -> {
398 executed.set(true);
399 });
400 vthread2.start();
401 vthread2.join();
402 assertTrue(executed.get());
403 }
404 } finally {
405 vthread1.join();
406 }
407 }
408 }
409
410 /**
411 * Test lots of virtual threads blocked waiting to enter a monitor. If the number
412 * of virtual threads exceeds the number of carrier threads this test will hang if
413 * carriers aren't released.
414 */
415 @Test
416 @DisabledIf("LockingMode#isLegacy")
417 void testManyBlockedThreads() throws Exception {
418 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
419 var lock = new Object();
420 synchronized (lock) {
421 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
422 var started = new CountDownLatch(1);
423 var vthread = Thread.ofVirtual().start(() -> {
424 started.countDown();
425 synchronized (lock) {
426 }
427 });
428 // wait for thread to start and block
429 started.await();
430 await(vthread, Thread.State.BLOCKED);
431 vthreads[i] = vthread;
432 }
433 }
434
435 // cleanup
436 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
437 vthreads[i].join();
438 }
439 }
440
441 /**
442 * Returns a stream of elements that are ordered pairs of platform and virtual thread
443 * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
444 */
445 static Stream<Arguments> threadCounts() {
446 return IntStream.range(0, 17)
447 .filter(i -> i % 2 == 0)
448 .mapToObj(i -> i)
449 .flatMap(np -> IntStream.range(2, 33)
450 .filter(i -> i % 2 == 0)
451 .mapToObj(vp -> Arguments.of(np, vp)));
452 }
453
454 /**
455 * Test mutual exclusion of monitors with platform and virtual threads.
456 */
457 @ParameterizedTest
458 @MethodSource("threadCounts")
459 void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
460 class Counter {
|