1 /* 2 * Copyright (c) 2019, 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 threads using Object.wait/notifyAll 27 * @modules java.base/java.lang:+open jdk.management 28 * @library /test/lib 29 * @build LockingMode 30 * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorWaitNotify 31 */ 32 33 /* 34 * @test id=LM_LEGACY 35 * @modules java.base/java.lang:+open jdk.management 36 * @library /test/lib 37 * @build LockingMode 38 * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 39 */ 40 41 /* 42 * @test id=LM_LIGHTWEIGHT 43 * @modules java.base/java.lang:+open jdk.management 44 * @library /test/lib 45 * @build LockingMode 46 * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 47 */ 48 49 /* 50 * @test id=Xint-LM_LEGACY 51 * @modules java.base/java.lang:+open jdk.management 52 * @library /test/lib 53 * @build LockingMode 54 * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 55 */ 56 57 /* 58 * @test id=Xint-LM_LIGHTWEIGHT 59 * @modules java.base/java.lang:+open jdk.management 60 * @library /test/lib 61 * @build LockingMode 62 * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 63 */ 64 65 /* 66 * @test id=Xcomp-LM_LEGACY 67 * @modules java.base/java.lang:+open jdk.management 68 * @library /test/lib 69 * @build LockingMode 70 * @run junit/othervm -Xcomp -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 71 */ 72 73 /* 74 * @test id=Xcomp-LM_LIGHTWEIGHT 75 * @modules java.base/java.lang:+open jdk.management 76 * @library /test/lib 77 * @build LockingMode 78 * @run junit/othervm -Xcomp -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 79 */ 80 81 /* 82 * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY 83 * @modules java.base/java.lang:+open jdk.management 84 * @library /test/lib 85 * @build LockingMode 86 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 87 */ 88 89 /* 90 * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT 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=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 95 */ 96 97 /* 98 * @test id=Xcomp-noTieredCompilation-LM_LEGACY 99 * @modules java.base/java.lang:+open jdk.management 100 * @library /test/lib 101 * @build LockingMode 102 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 103 */ 104 105 /* 106 * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT 107 * @modules java.base/java.lang:+open jdk.management 108 * @library /test/lib 109 * @build LockingMode 110 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorWaitNotify 111 */ 112 113 import java.util.ArrayList; 114 import java.util.List; 115 import java.util.Set; 116 import java.util.concurrent.CountDownLatch; 117 import java.util.concurrent.Executors; 118 import java.util.concurrent.ExecutorService; 119 import java.util.concurrent.ThreadFactory; 120 import java.util.concurrent.TimeUnit; 121 import java.util.concurrent.atomic.AtomicBoolean; 122 import java.util.concurrent.atomic.AtomicInteger; 123 import java.util.concurrent.atomic.AtomicReference; 124 import java.util.concurrent.locks.LockSupport; 125 import java.util.stream.IntStream; 126 import java.util.stream.Stream; 127 import java.util.stream.Collectors; 128 129 import jdk.test.lib.thread.VThreadScheduler; 130 import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management 131 import jdk.test.lib.thread.VThreadPinner; 132 import org.junit.jupiter.api.Test; 133 import org.junit.jupiter.api.BeforeAll; 134 import org.junit.jupiter.api.condition.DisabledIf; 135 import org.junit.jupiter.params.provider.Arguments; 136 import org.junit.jupiter.params.ParameterizedTest; 137 import org.junit.jupiter.params.provider.MethodSource; 138 import org.junit.jupiter.params.provider.ValueSource; 139 import static org.junit.jupiter.api.Assertions.*; 140 import static org.junit.jupiter.api.Assumptions.*; 141 142 class MonitorWaitNotify { 143 144 @BeforeAll 145 static void setup() { 146 // need >=2 carriers for testing pinning 147 VThreadRunner.ensureParallelism(2); 148 } 149 150 /** 151 * Test virtual thread waits, notified by platform thread. 152 */ 153 @ParameterizedTest 154 @ValueSource(booleans = { true, false }) 155 void testWaitNotify1(boolean pinned) throws Exception { 156 var lock = new Object(); 157 var ready = new AtomicBoolean(); 158 var thread = Thread.ofVirtual().start(() -> { 159 synchronized (lock) { 160 try { 161 if (pinned) { 162 VThreadPinner.runPinned(() -> { 163 ready.set(true); 164 lock.wait(); 165 }); 166 } else { 167 ready.set(true); 168 lock.wait(); 169 } 170 } catch (InterruptedException e) { } 171 } 172 }); 173 awaitTrue(ready); 174 175 // notify, thread should block waiting to reenter 176 synchronized (lock) { 177 lock.notifyAll(); 178 await(thread, Thread.State.BLOCKED); 179 } 180 thread.join(); 181 } 182 183 /** 184 * Test platform thread waits, notified by virtual thread. 185 */ 186 @Test 187 void testWaitNotify2() throws Exception { 188 var lock = new Object(); 189 var thread = Thread.ofVirtual().unstarted(() -> { 190 synchronized (lock) { 191 lock.notifyAll(); 192 } 193 }); 194 synchronized (lock) { 195 thread.start(); 196 lock.wait(); 197 } 198 thread.join(); 199 } 200 201 /** 202 * Test virtual thread waits, notified by another virtual thread. 203 */ 204 @ParameterizedTest 205 @ValueSource(booleans = { true, false }) 206 void testWaitNotify3(boolean pinned) throws Exception { 207 var lock = new Object(); 208 var ready = new AtomicBoolean(); 209 var thread1 = Thread.ofVirtual().start(() -> { 210 synchronized (lock) { 211 try { 212 if (pinned) { 213 VThreadPinner.runPinned(() -> { 214 ready.set(true); 215 lock.wait(); 216 }); 217 } else { 218 ready.set(true); 219 lock.wait(); 220 } 221 } catch (InterruptedException e) { 222 e.printStackTrace(); 223 } 224 } 225 }); 226 var thread2 = Thread.ofVirtual().start(() -> { 227 try { 228 awaitTrue(ready); 229 230 // notify, thread should block waiting to reenter 231 synchronized (lock) { 232 lock.notifyAll(); 233 await(thread1, Thread.State.BLOCKED); 234 } 235 } catch (InterruptedException e) { 236 e.printStackTrace(); 237 } 238 }); 239 thread1.join(); 240 thread2.join(); 241 } 242 243 /** 244 * Test notifyAll when there are no threads waiting. 245 */ 246 @ParameterizedTest 247 @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE }) 248 void testNotifyBeforeWait(int timeout) throws Exception { 249 var lock = new Object(); 250 251 // no threads waiting 252 synchronized (lock) { 253 lock.notifyAll(); 254 } 255 256 var ready = new AtomicBoolean(); 257 var thread = Thread.ofVirtual().start(() -> { 258 try { 259 synchronized (lock) { 260 ready.set(true); 261 262 // thread should wait 263 if (timeout > 0) { 264 lock.wait(timeout); 265 } else { 266 lock.wait(); 267 } 268 } 269 } catch (InterruptedException e) { } 270 }); 271 272 try { 273 // wait for thread to start and wait 274 awaitTrue(ready); 275 Thread.State expectedState = timeout > 0 276 ? Thread.State.TIMED_WAITING 277 : Thread.State.WAITING; 278 await(thread, expectedState); 279 280 // poll thread state again, it should still be waiting 281 Thread.sleep(10); 282 assertEquals(thread.getState(), expectedState); 283 } finally { 284 synchronized (lock) { 285 lock.notifyAll(); 286 } 287 thread.join(); 288 } 289 } 290 291 /** 292 * Returns a stream of elements that are ordered pairs of platform and virtual thread 293 * counts. 0,2,4,..8 platform threads. 2,4,6,..16 virtual threads. 294 */ 295 static Stream<Arguments> threadCounts() { 296 return IntStream.range(0, 9) 297 .filter(i -> i % 2 == 0) 298 .mapToObj(i -> i) 299 .flatMap(np -> IntStream.range(2, 17) 300 .filter(i -> i % 2 == 0) 301 .mapToObj(vp -> Arguments.of(np, vp))); 302 } 303 304 /** 305 * Test notify wakes only one thread when platform and virtual threads are waiting. 306 */ 307 @ParameterizedTest 308 @MethodSource("threadCounts") 309 @DisabledIf("LockingMode#isLegacy") 310 void testNotifyOneThread(int nPlatformThreads, int nVirtualThreads) throws Exception { 311 int nThreads = nPlatformThreads + nVirtualThreads; 312 313 var lock = new Object(); 314 var ready = new CountDownLatch(nThreads); 315 var notified = new AtomicInteger(); 316 317 Runnable waitTask = () -> { 318 synchronized (lock) { 319 try { 320 ready.countDown(); 321 lock.wait(); 322 notified.incrementAndGet(); 323 } catch (InterruptedException e) { } 324 } 325 }; 326 327 var threads = new ArrayList<Thread>(); 328 try { 329 for (int i = 0; i < nPlatformThreads; i++) { 330 threads.add(Thread.ofPlatform().start(waitTask)); 331 } 332 for (int i = 0; i < nVirtualThreads; i++) { 333 threads.add(Thread.ofVirtual().start(waitTask)); 334 } 335 336 // wait for all threads to wait 337 ready.await(); 338 339 // wake threads, one by one 340 for (int i = 0; i < threads.size(); i++) { 341 342 // wake one thread 343 synchronized (lock) { 344 lock.notify(); 345 } 346 347 // one thread should have awoken 348 int expectedWakeups = i + 1; 349 while (notified.get() < expectedWakeups) { 350 Thread.sleep(10); 351 } 352 assertEquals(expectedWakeups, notified.get()); 353 } 354 } finally { 355 for (Thread t : threads) { 356 t.interrupt(); 357 t.join(); 358 } 359 } 360 } 361 362 /** 363 * Test notifyAll wakes all threads. 364 */ 365 @ParameterizedTest 366 @MethodSource("threadCounts") 367 @DisabledIf("LockingMode#isLegacy") 368 void testNotifyAllThreads(int nPlatformThreads, int nVirtualThreads) throws Exception { 369 int nThreads = nPlatformThreads + nVirtualThreads; 370 371 var lock = new Object(); 372 var ready = new CountDownLatch(nThreads); 373 var notified = new CountDownLatch(nThreads); 374 375 Runnable waitTask = () -> { 376 synchronized (lock) { 377 try { 378 ready.countDown(); 379 lock.wait(); 380 notified.countDown(); 381 } catch (InterruptedException e) { } 382 } 383 }; 384 385 var threads = new ArrayList<Thread>(); 386 try { 387 for (int i = 0; i < nPlatformThreads; i++) { 388 threads.add(Thread.ofPlatform().start(waitTask)); 389 } 390 for (int i = 0; i < nVirtualThreads; i++) { 391 threads.add(Thread.ofVirtual().start(waitTask)); 392 } 393 394 // wait for all threads to wait 395 ready.await(); 396 397 // wakeup all threads 398 synchronized (lock) { 399 lock.notifyAll(); 400 } 401 402 // wait for all threads to have awoken 403 notified.await(); 404 405 } finally { 406 for (Thread t : threads) { 407 t.interrupt(); 408 t.join(); 409 } 410 } 411 } 412 413 /** 414 * Test duration of timed Object.wait. 415 */ 416 @Test 417 void testTimedWaitDuration1() throws Exception { 418 var lock = new Object(); 419 420 var durationRef = new AtomicReference<Long>(); 421 var thread = Thread.ofVirtual().start(() -> { 422 try { 423 synchronized (lock) { 424 long start = millisTime(); 425 lock.wait(2000); 426 durationRef.set(millisTime() - start); 427 } 428 } catch (InterruptedException e) { } 429 }); 430 431 thread.join(); 432 433 long duration = durationRef.get(); 434 checkDuration(duration, 1900, 20_000); 435 } 436 437 /** 438 * Test duration of timed Object.wait. This test invokes wait twice, first with a short 439 * timeout, the second with a longer timeout. The test scenario ensures that the 440 * timeout from the first wait doesn't interfere with the second wait. 441 */ 442 @Test 443 void testTimedWaitDuration2() throws Exception { 444 var lock = new Object(); 445 446 var ready = new AtomicBoolean(); 447 var waited = new AtomicBoolean(); 448 var durationRef = new AtomicReference<Long>(); 449 var thread = Thread.ofVirtual().start(() -> { 450 try { 451 synchronized (lock) { 452 ready.set(true); 453 lock.wait(200); 454 waited.set(true); 455 456 long start = millisTime(); 457 lock.wait(2000); 458 durationRef.set(millisTime() - start); 459 } 460 } catch (InterruptedException e) { } 461 }); 462 463 awaitTrue(ready); 464 synchronized (lock) { 465 // wake thread if waiting in first wait 466 if (!waited.get()) { 467 lock.notifyAll(); 468 } 469 } 470 471 thread.join(); 472 473 long duration = durationRef.get(); 474 checkDuration(duration, 1900, 20_000); 475 } 476 477 /** 478 * Testing invoking Object.wait with interrupt status set. 479 */ 480 @ParameterizedTest 481 @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE }) 482 void testWaitWithInterruptSet(int timeout) throws Exception { 483 VThreadRunner.run(() -> { 484 Object lock = new Object(); 485 synchronized (lock) { 486 Thread.currentThread().interrupt(); 487 if (timeout > 0) { 488 assertThrows(InterruptedException.class, () -> lock.wait(timeout)); 489 } else { 490 assertThrows(InterruptedException.class, lock::wait); 491 } 492 assertFalse(Thread.currentThread().isInterrupted()); 493 } 494 }); 495 } 496 497 /** 498 * Test interrupting a virtual thread waiting in Object.wait. 499 */ 500 @ParameterizedTest 501 @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE }) 502 void testInterruptWait(int timeout) throws Exception { 503 var lock = new Object(); 504 var ready = new AtomicBoolean(); 505 var interruptedException = new AtomicBoolean(); 506 var vthread = Thread.ofVirtual().start(() -> { 507 synchronized (lock) { 508 try { 509 ready.set(true); 510 if (timeout > 0) { 511 lock.wait(timeout); 512 } else { 513 lock.wait(); 514 } 515 } catch (InterruptedException e) { 516 // check stack trace has the expected frames 517 Set<String> expected = Set.of("wait0", "wait", "run"); 518 Set<String> methods = Stream.of(e.getStackTrace()) 519 .map(StackTraceElement::getMethodName) 520 .collect(Collectors.toSet()); 521 assertTrue(methods.containsAll(expected)); 522 523 interruptedException.set(true); 524 } 525 } 526 }); 527 528 // wait for thread to start and wait 529 awaitTrue(ready); 530 await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING); 531 532 // interrupt thread, should block, then throw InterruptedException 533 synchronized (lock) { 534 vthread.interrupt(); 535 await(vthread, Thread.State.BLOCKED); 536 } 537 vthread.join(); 538 assertTrue(interruptedException.get()); 539 } 540 541 /** 542 * Test interrupting a virtual thread blocked waiting to reenter after waiting. 543 */ 544 @ParameterizedTest 545 @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE }) 546 void testInterruptReenterAfterWait(int timeout) throws Exception { 547 var lock = new Object(); 548 var ready = new AtomicBoolean(); 549 var interruptedException = new AtomicBoolean(); 550 var vthread = Thread.ofVirtual().start(() -> { 551 synchronized (lock) { 552 try { 553 ready.set(true); 554 if (timeout > 0) { 555 lock.wait(timeout); 556 } else { 557 lock.wait(); 558 } 559 } catch (InterruptedException e) { 560 interruptedException.set(true); 561 } 562 } 563 }); 564 565 // wait for thread to start and wait 566 awaitTrue(ready); 567 await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING); 568 569 // notify, thread should block waiting to reenter 570 synchronized (lock) { 571 lock.notifyAll(); 572 await(vthread, Thread.State.BLOCKED); 573 574 // interrupt when blocked 575 vthread.interrupt(); 576 } 577 578 vthread.join(); 579 assertFalse(interruptedException.get()); 580 assertTrue(vthread.isInterrupted()); 581 } 582 583 /** 584 * Test Object.wait when the monitor entry count > 1. 585 */ 586 @ParameterizedTest 587 @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE }) 588 void testWaitWhenEnteredManyTimes(int timeout) throws Exception { 589 var lock = new Object(); 590 var ready = new AtomicBoolean(); 591 var vthread = Thread.ofVirtual().start(() -> { 592 synchronized (lock) { 593 synchronized (lock) { 594 synchronized (lock) { 595 try { 596 ready.set(true); 597 if (timeout > 0) { 598 lock.wait(timeout); 599 } else { 600 lock.wait(); 601 } 602 } catch (InterruptedException e) { } 603 } 604 } 605 } 606 }); 607 608 // wait for thread to start and wait 609 awaitTrue(ready); 610 await(vthread, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING); 611 612 // notify, thread should block waiting to reenter 613 synchronized (lock) { 614 lock.notifyAll(); 615 await(vthread, Thread.State.BLOCKED); 616 } 617 vthread.join(); 618 } 619 620 /** 621 * Test that Object.wait does not consume the thread's parking permit. 622 */ 623 @Test 624 void testParkingPermitNotConsumed() throws Exception { 625 var lock = new Object(); 626 var started = new CountDownLatch(1); 627 var completed = new AtomicBoolean(); 628 var vthread = Thread.ofVirtual().start(() -> { 629 started.countDown(); 630 LockSupport.unpark(Thread.currentThread()); 631 synchronized (lock) { 632 try { 633 lock.wait(); 634 } catch (InterruptedException e) { 635 fail("wait interrupted"); 636 } 637 } 638 LockSupport.park(); // should not park 639 completed.set(true); 640 }); 641 642 // wait for thread to start and wait 643 started.await(); 644 await(vthread, Thread.State.WAITING); 645 646 // wakeup thread 647 synchronized (lock) { 648 lock.notifyAll(); 649 } 650 651 // thread should terminate 652 vthread.join(); 653 assertTrue(completed.get()); 654 } 655 656 /** 657 * Test that Object.wait does not make available the thread's parking permit. 658 */ 659 @Test 660 void testParkingPermitNotOffered() throws Exception { 661 var lock = new Object(); 662 var started = new CountDownLatch(1); 663 var readyToPark = new CountDownLatch(1); 664 var completed = new AtomicBoolean(); 665 var vthread = Thread.ofVirtual().start(() -> { 666 started.countDown(); 667 synchronized (lock) { 668 try { 669 lock.wait(); 670 } catch (InterruptedException e) { 671 fail("wait interrupted"); 672 } 673 } 674 readyToPark.countDown(); 675 LockSupport.park(); // should park 676 completed.set(true); 677 }); 678 679 // wait for thread to start and wait 680 started.await(); 681 await(vthread, Thread.State.WAITING); 682 683 // wakeup thread 684 synchronized (lock) { 685 lock.notifyAll(); 686 } 687 688 // thread should park 689 readyToPark.await(); 690 await(vthread, Thread.State.WAITING); 691 692 LockSupport.unpark(vthread); 693 694 // thread should terminate 695 vthread.join(); 696 assertTrue(completed.get()); 697 } 698 699 /** 700 * Test that Object.wait releases the carrier. This test uses a custom scheduler 701 * with one carrier thread. 702 */ 703 @ParameterizedTest 704 @ValueSource(ints = { 0, 30000, Integer.MAX_VALUE }) 705 @DisabledIf("LockingMode#isLegacy") 706 void testReleaseWhenWaiting1(int timeout) throws Exception { 707 assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers"); 708 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { 709 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler); 710 711 var lock = new Object(); 712 var ready = new AtomicBoolean(); 713 var completed = new AtomicBoolean(); 714 715 var vthread1 = factory.newThread(() -> { 716 synchronized (lock) { 717 try { 718 ready.set(true); 719 if (timeout > 0) { 720 lock.wait(timeout); 721 } else { 722 lock.wait(); 723 } 724 } catch (InterruptedException e) { 725 fail("wait interrupted"); 726 } 727 } 728 completed.set(true); 729 }); 730 vthread1.start(); 731 732 // wait for vthread1 to start and wait 733 awaitTrue(ready); 734 await(vthread1, timeout > 0 ? Thread.State.TIMED_WAITING : Thread.State.WAITING); 735 736 // carrier should be released, use it for another thread 737 var executed = new AtomicBoolean(); 738 var vthread2 = factory.newThread(() -> { 739 executed.set(true); 740 }); 741 vthread2.start(); 742 vthread2.join(); 743 assertTrue(executed.get()); 744 745 // wakeup vthread1 746 synchronized (lock) { 747 lock.notifyAll(); 748 } 749 750 vthread1.join(); 751 assertTrue(completed.get()); 752 } 753 } 754 755 /** 756 * Test that Object.wait releases the carrier. This test arranges for 4*ncores - 1 757 * virtual threads to wait. For long timeout and no timeout cases, all virtual threads 758 * will wait until they are notified. 759 */ 760 @ParameterizedTest 761 @ValueSource(ints = { 0, 10, 20, 100, 500, 30000, Integer.MAX_VALUE }) 762 @DisabledIf("LockingMode#isLegacy") 763 void testReleaseWhenWaiting2(int timeout) throws Exception { 764 int VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors(); 765 CountDownLatch latch = new CountDownLatch(VTHREAD_COUNT); 766 Object lock = new Object(); 767 AtomicInteger counter = new AtomicInteger(0); 768 769 for (int i = 0; i < VTHREAD_COUNT; i++) { 770 Thread.ofVirtual().name("vthread-" + i).start(() -> { 771 synchronized (lock) { 772 if (counter.incrementAndGet() == VTHREAD_COUNT) { 773 lock.notifyAll(); 774 } else { 775 try { 776 if (timeout > 0) { 777 lock.wait(timeout); 778 } else { 779 lock.wait(); 780 } 781 } catch (InterruptedException e) {} 782 } 783 } 784 latch.countDown(); 785 }); 786 } 787 latch.await(); 788 } 789 790 /** 791 * Test that wait(long) throws IAE when timeout is negative. 792 */ 793 @Test 794 void testIllegalArgumentException() throws Exception { 795 VThreadRunner.run(() -> { 796 Object obj = new Object(); 797 synchronized (obj) { 798 assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L)); 799 assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L)); 800 assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE)); 801 } 802 }); 803 } 804 805 /** 806 * Test that wait throws IMSE when not owner. 807 */ 808 @Test 809 void testIllegalMonitorStateException() throws Exception { 810 VThreadRunner.run(() -> { 811 Object obj = new Object(); 812 assertThrows(IllegalMonitorStateException.class, () -> obj.wait()); 813 assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0)); 814 assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000)); 815 assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE)); 816 }); 817 } 818 819 /** 820 * Waits for the boolean value to become true. 821 */ 822 private static void awaitTrue(AtomicBoolean ref) throws InterruptedException { 823 while (!ref.get()) { 824 Thread.sleep(20); 825 } 826 } 827 828 /** 829 * Waits for the given thread to reach a given state. 830 */ 831 private void await(Thread thread, Thread.State expectedState) throws InterruptedException { 832 Thread.State state = thread.getState(); 833 while (state != expectedState) { 834 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 835 Thread.sleep(10); 836 state = thread.getState(); 837 } 838 } 839 840 /** 841 * Returns the current time in milliseconds. 842 */ 843 private static long millisTime() { 844 long now = System.nanoTime(); 845 return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); 846 } 847 848 /** 849 * Check a duration is within expected bounds. 850 * @param duration, in milliseconds 851 * @param min minimum expected duration, in milliseconds 852 * @param max maximum expected duration, in milliseconds 853 * @return the duration (now - start), in milliseconds 854 */ 855 private static void checkDuration(long duration, long min, long max) { 856 assertTrue(duration >= min, 857 "Duration " + duration + "ms, expected >= " + min + "ms"); 858 assertTrue(duration <= max, 859 "Duration " + duration + "ms, expected <= " + max + "ms"); 860 } 861 } --- EOF ---