1 /* 2 * Copyright (c) 2023, 2024, 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 id=default 26 * @summary Test virtual thread with monitor enter/exit 27 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 28 * @modules java.base/java.lang:+open 29 * @library /test/lib 30 * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit 31 */ 32 33 /* 34 * @test id=LM_LEGACY 35 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 36 * @modules java.base/java.lang:+open 37 * @library /test/lib 38 * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit 39 */ 40 41 /* 42 * @test id=LM_LIGHTWEIGHT 43 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 44 * @modules java.base/java.lang:+open 45 * @library /test/lib 46 * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit 47 */ 48 49 /* 50 * @test id=Xint-LM_LEGACY 51 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 52 * @modules java.base/java.lang:+open 53 * @library /test/lib 54 * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit 55 */ 56 57 /* 58 * @test id=Xint-LM_LIGHTWEIGHT 59 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 60 * @modules java.base/java.lang:+open 61 * @library /test/lib 62 * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit 63 */ 64 65 /* 66 * @test id=Xcomp-LM_LEGACY 67 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 68 * @modules java.base/java.lang:+open 69 * @library /test/lib 70 * @run junit/othervm -Xcomp -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit 71 */ 72 73 /* 74 * @test id=Xcomp-LM_LIGHTWEIGHT 75 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 76 * @modules java.base/java.lang:+open 77 * @library /test/lib 78 * @run junit/othervm -Xcomp -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit 79 */ 80 81 /* 82 * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY 83 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 84 * @modules java.base/java.lang:+open 85 * @library /test/lib 86 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit 87 */ 88 89 /* 90 * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT 91 * @modules java.base/java.lang:+open 92 * @library /test/lib 93 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit 94 */ 95 96 /* 97 * @test id=Xcomp-noTieredCompilation-LM_LEGACY 98 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 99 * @modules java.base/java.lang:+open 100 * @library /test/lib 101 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit 102 */ 103 104 /* 105 * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT 106 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" 107 * @modules java.base/java.lang:+open 108 * @library /test/lib 109 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit 110 */ 111 112 import java.time.Duration; 113 import java.util.ArrayList; 114 import java.util.List; 115 import java.util.concurrent.CountDownLatch; 116 import java.util.concurrent.ThreadFactory; 117 import java.util.concurrent.ThreadLocalRandom; 118 import java.util.concurrent.Executors; 119 import java.util.concurrent.ExecutorService; 120 import java.util.concurrent.atomic.AtomicBoolean; 121 import java.util.concurrent.locks.LockSupport; 122 import java.util.stream.IntStream; 123 import java.util.stream.Stream; 124 125 import jdk.test.lib.thread.VThreadPinner; 126 import jdk.test.lib.thread.VThreadRunner; 127 import jdk.test.lib.thread.VThreadScheduler; 128 import org.junit.jupiter.api.Test; 129 import org.junit.jupiter.api.BeforeAll; 130 import org.junit.jupiter.api.RepeatedTest; 131 import org.junit.jupiter.params.ParameterizedTest; 132 import org.junit.jupiter.params.provider.Arguments; 133 import org.junit.jupiter.params.provider.ValueSource; 134 import org.junit.jupiter.params.provider.MethodSource; 135 import org.junit.jupiter.api.condition.*; 136 import static org.junit.jupiter.api.Assertions.*; 137 import static org.junit.jupiter.api.Assumptions.*; 138 139 class MonitorEnterExit { 140 static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors(); 141 static final int MAX_ENTER_DEPTH = 256; 142 143 @BeforeAll 144 static void setup() { 145 // need >=2 carriers for testing pinning when main thread is a virtual thread 146 if (Thread.currentThread().isVirtual()) { 147 VThreadRunner.ensureParallelism(2); 148 } 149 } 150 151 /** 152 * Test monitor enter with no contention. 153 */ 154 @Test 155 void testEnterNoContention() throws Exception { 156 var lock = new Object(); 157 VThreadRunner.run(() -> { 158 synchronized (lock) { 159 assertTrue(Thread.holdsLock(lock)); 160 } 161 assertFalse(Thread.holdsLock(lock)); 162 }); 163 } 164 165 /** 166 * Test monitor enter with contention, monitor is held by platform thread. 167 */ 168 @Test 169 void testEnterWhenHeldByPlatformThread() throws Exception { 170 testEnterWithContention(); 171 } 172 173 /** 174 * Test monitor enter with contention, monitor is held by virtual thread. 175 */ 176 @Test 177 void testEnterWhenHeldByVirtualThread() throws Exception { 178 VThreadRunner.run(this::testEnterWithContention); 179 } 180 181 /** 182 * Test monitor enter with contention, monitor will be held by caller thread. 183 */ 184 private void testEnterWithContention() throws Exception { 185 var lock = new Object(); 186 var started = new CountDownLatch(1); 187 var entered = new AtomicBoolean(); 188 var vthread = Thread.ofVirtual().unstarted(() -> { 189 started.countDown(); 190 synchronized (lock) { 191 assertTrue(Thread.holdsLock(lock)); 192 entered.set(true); 193 } 194 assertFalse(Thread.holdsLock(lock)); 195 }); 196 try { 197 synchronized (lock) { 198 vthread.start(); 199 200 // wait for thread to start and block 201 started.await(); 202 await(vthread, Thread.State.BLOCKED); 203 204 assertFalse(entered.get()); 205 } 206 } finally { 207 vthread.join(); 208 } 209 assertTrue(entered.get()); 210 } 211 212 /** 213 * Test monitor reenter. 214 */ 215 @Test 216 void testReenter() throws Exception { 217 var lock = new Object(); 218 VThreadRunner.run(() -> { 219 testReenter(lock, 0); 220 assertFalse(Thread.holdsLock(lock)); 221 }); 222 } 223 224 private void testReenter(Object lock, int depth) { 225 if (depth < MAX_ENTER_DEPTH) { 226 synchronized (lock) { 227 assertTrue(Thread.holdsLock(lock)); 228 testReenter(lock, depth + 1); 229 assertTrue(Thread.holdsLock(lock)); 230 } 231 } 232 } 233 234 /** 235 * Test monitor reenter when there are other threads blocked trying to enter. 236 */ 237 @Test 238 void testReenterWithContention() throws Exception { 239 var lock = new Object(); 240 VThreadRunner.run(() -> { 241 List<Thread> threads = new ArrayList<>(); 242 testReenter(lock, 0, threads); 243 244 // wait for threads to terminate 245 for (Thread vthread : threads) { 246 vthread.join(); 247 } 248 }); 249 } 250 251 private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception { 252 if (depth < MAX_ENTER_DEPTH) { 253 synchronized (lock) { 254 assertTrue(Thread.holdsLock(lock)); 255 256 // start platform or virtual thread that blocks waiting to enter 257 var started = new CountDownLatch(1); 258 ThreadFactory factory = ThreadLocalRandom.current().nextBoolean() 259 ? Thread.ofPlatform().factory() 260 : Thread.ofVirtual().factory(); 261 var thread = factory.newThread(() -> { 262 started.countDown(); 263 synchronized (lock) { 264 /* do nothing */ 265 } 266 }); 267 thread.start(); 268 269 // wait for thread to start and block 270 started.await(); 271 await(thread, Thread.State.BLOCKED); 272 threads.add(thread); 273 274 // test reenter 275 testReenter(lock, depth + 1, threads); 276 } 277 } 278 } 279 280 /** 281 * Test monitor enter when pinned. 282 */ 283 @Test 284 void testEnterWhenPinned() throws Exception { 285 var lock = new Object(); 286 VThreadPinner.runPinned(() -> { 287 synchronized (lock) { 288 assertTrue(Thread.holdsLock(lock)); 289 } 290 assertFalse(Thread.holdsLock(lock)); 291 }); 292 } 293 294 /** 295 * Test monitor reenter when pinned. 296 */ 297 @Test 298 void testReenterWhenPinned() throws Exception { 299 VThreadRunner.run(() -> { 300 var lock = new Object(); 301 synchronized (lock) { 302 VThreadPinner.runPinned(() -> { 303 assertTrue(Thread.holdsLock(lock)); 304 synchronized (lock) { 305 assertTrue(Thread.holdsLock(lock)); 306 } 307 assertTrue(Thread.holdsLock(lock)); 308 }); 309 } 310 assertFalse(Thread.holdsLock(lock)); 311 }); 312 } 313 314 /** 315 * Test contended monitor enter when pinned. Monitor is held by platform thread. 316 */ 317 @Test 318 void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception { 319 testEnterWithContentionWhenPinned(); 320 } 321 322 /** 323 * Test contended monitor enter when pinned. Monitor is held by virtual thread. 324 */ 325 @Test 326 void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception { 327 // need at least two carrier threads 328 int previousParallelism = VThreadRunner.ensureParallelism(2); 329 try { 330 VThreadRunner.run(this::testEnterWithContentionWhenPinned); 331 } finally { 332 VThreadRunner.setParallelism(previousParallelism); 333 } 334 } 335 336 /** 337 * Test contended monitor enter when pinned, monitor will be held by caller thread. 338 */ 339 private void testEnterWithContentionWhenPinned() throws Exception { 340 var lock = new Object(); 341 var started = new CountDownLatch(1); 342 var entered = new AtomicBoolean(); 343 Thread vthread = Thread.ofVirtual().unstarted(() -> { 344 VThreadPinner.runPinned(() -> { 345 started.countDown(); 346 synchronized (lock) { 347 entered.set(true); 348 } 349 }); 350 }); 351 synchronized (lock) { 352 // start thread and wait for it to block 353 vthread.start(); 354 started.await(); 355 await(vthread, Thread.State.BLOCKED); 356 assertFalse(entered.get()); 357 } 358 vthread.join(); 359 360 // check thread entered monitor 361 assertTrue(entered.get()); 362 } 363 364 /** 365 * Test that parking while holding a monitor releases the carrier. 366 */ 367 @ParameterizedTest 368 @ValueSource(booleans = { true, false }) 369 void testReleaseWhenParked(boolean reenter) throws Exception { 370 assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers"); 371 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { 372 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler); 373 374 var lock = new Object(); 375 376 // thread enters (and maybe reenters) a monitor and parks 377 var started = new CountDownLatch(1); 378 var vthread1 = factory.newThread(() -> { 379 started.countDown(); 380 synchronized (lock) { 381 if (reenter) { 382 synchronized (lock) { 383 LockSupport.park(); 384 } 385 } else { 386 LockSupport.park(); 387 } 388 } 389 }); 390 391 vthread1.start(); 392 try { 393 // wait for thread to start and park 394 started.await(); 395 await(vthread1, Thread.State.WAITING); 396 397 // carrier should be released, use it for another thread 398 var executed = new AtomicBoolean(); 399 var vthread2 = factory.newThread(() -> { 400 executed.set(true); 401 }); 402 vthread2.start(); 403 vthread2.join(); 404 assertTrue(executed.get()); 405 } finally { 406 LockSupport.unpark(vthread1); 407 vthread1.join(); 408 } 409 } 410 } 411 412 /** 413 * Test that blocking waiting to enter a monitor releases the carrier. 414 */ 415 @Test 416 void testReleaseWhenBlocked() throws Exception { 417 assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers"); 418 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { 419 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler); 420 421 var lock = new Object(); 422 423 // thread enters monitor 424 var started = new CountDownLatch(1); 425 var vthread1 = factory.newThread(() -> { 426 started.countDown(); 427 synchronized (lock) { 428 } 429 }); 430 431 try { 432 synchronized (lock) { 433 // start thread and wait for it to block 434 vthread1.start(); 435 started.await(); 436 await(vthread1, Thread.State.BLOCKED); 437 438 // carrier should be released, use it for another thread 439 var executed = new AtomicBoolean(); 440 var vthread2 = factory.newThread(() -> { 441 executed.set(true); 442 }); 443 vthread2.start(); 444 vthread2.join(); 445 assertTrue(executed.get()); 446 } 447 } finally { 448 vthread1.join(); 449 } 450 } 451 } 452 453 /** 454 * Test lots of virtual threads parked while holding a monitor. If the number of 455 * virtual threads exceeds the number of carrier threads then this test will hang if 456 * parking doesn't release the carrier. 457 */ 458 @Test 459 void testManyParkedThreads() throws Exception { 460 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 461 var done = new AtomicBoolean(); 462 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 463 var lock = new Object(); 464 var started = new CountDownLatch(1); 465 var vthread = Thread.ofVirtual().start(() -> { 466 started.countDown(); 467 synchronized (lock) { 468 while (!done.get()) { 469 LockSupport.park(); 470 } 471 } 472 }); 473 // wait for thread to start and park 474 started.await(); 475 await(vthread, Thread.State.WAITING); 476 vthreads[i] = vthread; 477 } 478 479 // cleanup 480 done.set(true); 481 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 482 var vthread = vthreads[i]; 483 LockSupport.unpark(vthread); 484 vthread.join(); 485 } 486 } 487 488 /** 489 * Test lots of virtual threads blocked waiting to enter a monitor. If the number 490 * of virtual threads exceeds the number of carrier threads this test will hang if 491 * carriers aren't released. 492 */ 493 @Test 494 void testManyBlockedThreads() throws Exception { 495 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 496 var lock = new Object(); 497 synchronized (lock) { 498 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 499 var started = new CountDownLatch(1); 500 var vthread = Thread.ofVirtual().start(() -> { 501 started.countDown(); 502 synchronized (lock) { 503 } 504 }); 505 // wait for thread to start and block 506 started.await(); 507 await(vthread, Thread.State.BLOCKED); 508 vthreads[i] = vthread; 509 } 510 } 511 512 // cleanup 513 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 514 vthreads[i].join(); 515 } 516 } 517 518 /** 519 * Returns a stream of elements that are ordered pairs of platform and virtual thread 520 * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads. 521 */ 522 static Stream<Arguments> threadCounts() { 523 return IntStream.range(0, 17) 524 .filter(i -> i % 2 == 0) 525 .mapToObj(i -> i) 526 .flatMap(np -> IntStream.range(2, 33) 527 .filter(i -> i % 2 == 0) 528 .mapToObj(vp -> Arguments.of(np, vp))); 529 } 530 531 /** 532 * Test mutual exclusion of monitors with platform and virtual threads. 533 */ 534 @ParameterizedTest 535 @MethodSource("threadCounts") 536 void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception { 537 class Counter { 538 int count; 539 synchronized void increment() { 540 count++; 541 Thread.yield(); 542 } 543 } 544 var counter = new Counter(); 545 int nThreads = nPlatformThreads + nVirtualThreads; 546 var threads = new Thread[nThreads]; 547 int index = 0; 548 for (int i = 0; i < nPlatformThreads; i++) { 549 threads[index] = Thread.ofPlatform() 550 .name("platform-" + index) 551 .unstarted(counter::increment); 552 index++; 553 } 554 for (int i = 0; i < nVirtualThreads; i++) { 555 threads[index] = Thread.ofVirtual() 556 .name("virtual-" + index) 557 .unstarted(counter::increment); 558 index++; 559 } 560 // start all threads 561 for (Thread thread : threads) { 562 thread.start(); 563 } 564 // wait for all threads to terminate 565 for (Thread thread : threads) { 566 thread.join(); 567 } 568 assertEquals(nThreads, counter.count); 569 } 570 571 /** 572 * Test unblocking a virtual thread waiting to enter a monitor held by a platform thread. 573 */ 574 @RepeatedTest(20) 575 void testUnblockingByPlatformThread() throws Exception { 576 testUnblocking(); 577 } 578 579 /** 580 * Test unblocking a virtual thread waiting to enter a monitor held by another 581 * virtual thread. 582 */ 583 @RepeatedTest(20) 584 void testUnblockingByVirtualThread() throws Exception { 585 VThreadRunner.run(this::testUnblocking); 586 } 587 588 /** 589 * Test unblocking a virtual thread waiting to enter a monitor, monitor will be 590 * initially be held by caller thread. 591 */ 592 private void testUnblocking() throws Exception { 593 var lock = new Object(); 594 var started = new CountDownLatch(1); 595 var entered = new AtomicBoolean(); 596 var vthread = Thread.ofVirtual().unstarted(() -> { 597 started.countDown(); 598 synchronized (lock) { 599 entered.set(true); 600 } 601 }); 602 try { 603 synchronized (lock) { 604 vthread.start(); 605 started.await(); 606 607 // random delay before exiting monitor 608 switch (ThreadLocalRandom.current().nextInt(4)) { 609 case 0 -> { /* no delay */} 610 case 1 -> Thread.onSpinWait(); 611 case 2 -> Thread.yield(); 612 case 3 -> await(vthread, Thread.State.BLOCKED); 613 default -> fail(); 614 } 615 616 assertFalse(entered.get()); 617 } 618 } finally { 619 vthread.join(); 620 } 621 assertTrue(entered.get()); 622 } 623 624 /** 625 * Test that unblocking a virtual thread waiting to enter a monitor does not consume 626 * the thread's parking permit. 627 */ 628 @Test 629 void testParkingPermitNotConsumed() throws Exception { 630 var lock = new Object(); 631 var started = new CountDownLatch(1); 632 var vthread = Thread.ofVirtual().unstarted(() -> { 633 started.countDown(); 634 LockSupport.unpark(Thread.currentThread()); 635 synchronized (lock) { } // should block 636 LockSupport.park(); // should not park 637 }); 638 639 synchronized (lock) { 640 vthread.start(); 641 // wait for thread to start and block 642 started.await(); 643 await(vthread, Thread.State.BLOCKED); 644 } 645 vthread.join(); 646 } 647 648 /** 649 * Test that unblocking a virtual thread waiting to enter a monitor does not make 650 * available the thread's parking permit. 651 */ 652 @Test 653 void testParkingPermitNotOffered() throws Exception { 654 var lock = new Object(); 655 var started = new CountDownLatch(1); 656 var vthread = Thread.ofVirtual().unstarted(() -> { 657 started.countDown(); 658 synchronized (lock) { } // should block 659 LockSupport.park(); // should park 660 }); 661 662 synchronized (lock) { 663 vthread.start(); 664 // wait for thread to start and block 665 started.await(); 666 await(vthread, Thread.State.BLOCKED); 667 } 668 669 try { 670 // wait for thread to park, it should not terminate 671 await(vthread, Thread.State.WAITING); 672 vthread.join(Duration.ofMillis(100)); 673 assertEquals(Thread.State.WAITING, vthread.getState()); 674 } finally { 675 LockSupport.unpark(vthread); 676 vthread.join(); 677 } 678 } 679 680 /** 681 * Waits for the given thread to reach a given state. 682 */ 683 private void await(Thread thread, Thread.State expectedState) throws InterruptedException { 684 Thread.State state = thread.getState(); 685 while (state != expectedState) { 686 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 687 Thread.sleep(10); 688 state = thread.getState(); 689 } 690 } 691 }