1 /* 2 * Copyright (c) 2021, 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=platform 26 * @summary Basic tests for ThreadFlock 27 * @modules java.base/jdk.internal.misc 28 * @run junit/othervm -DthreadFactory=platform ThreadFlockTest 29 */ 30 31 /* 32 * @test id=virtual 33 * @modules java.base/jdk.internal.misc 34 * @run junit/othervm -DthreadFactory=virtual ThreadFlockTest 35 */ 36 37 import java.time.Duration; 38 import java.util.*; 39 import java.util.function.Function; 40 import java.util.concurrent.*; 41 import java.util.concurrent.atomic.AtomicBoolean; 42 import java.util.concurrent.atomic.AtomicReference; 43 import java.util.stream.Collectors; 44 import java.util.stream.Stream; 45 import jdk.internal.misc.ThreadFlock; 46 47 import org.junit.jupiter.api.Test; 48 import org.junit.jupiter.api.BeforeAll; 49 import org.junit.jupiter.api.AfterAll; 50 import org.junit.jupiter.params.ParameterizedTest; 51 import org.junit.jupiter.params.provider.MethodSource; 52 import static org.junit.jupiter.api.Assertions.*; 53 54 class ThreadFlockTest { 55 private static ScheduledExecutorService scheduler; 56 private static List<ThreadFactory> threadFactories; 57 58 @BeforeAll 59 static void setup() throws Exception { 60 scheduler = Executors.newSingleThreadScheduledExecutor(); 61 62 // thread factories 63 String value = System.getProperty("threadFactory"); 64 List<ThreadFactory> list = new ArrayList<>(); 65 if (value == null || value.equals("platform")) 66 list.add(Thread.ofPlatform().factory()); 67 if (value == null || value.equals("virtual")) 68 list.add(Thread.ofVirtual().factory()); 69 assertTrue(list.size() > 0, "No thread factories for tests"); 70 threadFactories = list; 71 } 72 73 @AfterAll 74 static void shutdown() { 75 scheduler.shutdown(); 76 } 77 78 private static Stream<ThreadFactory> factories() { 79 return threadFactories.stream(); 80 } 81 82 /** 83 * Test ThreadFlock::name. 84 */ 85 @Test 86 void testName() { 87 try (var flock = ThreadFlock.open(null)) { 88 assertNull(flock.name()); 89 flock.close(); 90 assertNull(flock.name()); // after close 91 } 92 try (var flock = ThreadFlock.open("fetcher")) { 93 assertEquals("fetcher", flock.name()); 94 flock.close(); 95 assertEquals("fetcher", flock.name()); // after close 96 } 97 } 98 99 /** 100 * Test ThreadFlock::owner. 101 */ 102 @Test 103 void testOwner() { 104 try (var flock = ThreadFlock.open(null)) { 105 assertTrue(flock.owner() == Thread.currentThread()); 106 flock.close(); 107 assertTrue(flock.owner() == Thread.currentThread()); // after close 108 } 109 } 110 111 /** 112 * Test ThreadFlock::isXXXX methods. 113 */ 114 @Test 115 void testState() { 116 try (var flock = ThreadFlock.open(null)) { 117 assertFalse(flock.isShutdown()); 118 assertFalse(flock.isClosed()); 119 flock.close(); 120 assertTrue(flock.isShutdown()); 121 assertTrue(flock.isClosed()); 122 } 123 try (var flock = ThreadFlock.open(null)) { 124 flock.shutdown(); 125 assertTrue(flock.isShutdown()); 126 assertFalse(flock.isClosed()); 127 flock.close(); 128 assertTrue(flock.isShutdown()); 129 assertTrue(flock.isClosed()); 130 } 131 } 132 133 /** 134 * Test ThreadFlock::threads enumerates all threads. 135 */ 136 @ParameterizedTest 137 @MethodSource("factories") 138 void testThreads(ThreadFactory factory) { 139 CountDownLatch latch = new CountDownLatch(1); 140 var exception = new AtomicReference<Exception>(); 141 Runnable awaitLatch = () -> { 142 try { 143 latch.await(); 144 } catch (Exception e) { 145 exception.compareAndSet(null, e); 146 } 147 }; 148 149 var flock = ThreadFlock.open(null); 150 try { 151 assertTrue(flock.threads().count() == 0); 152 153 // start 100 threads 154 Set<Thread> threads = new HashSet<>(); 155 for (int i = 0; i < 100; i++) { 156 Thread thread = factory.newThread(awaitLatch); 157 flock.start(thread); 158 threads.add(thread); 159 } 160 161 // check thread ThreadFlock::threads enumerates all threads 162 assertEquals(flock.threads().collect(Collectors.toSet()), threads); 163 164 } finally { 165 latch.countDown(); // release threads 166 flock.close(); 167 } 168 assertTrue(flock.threads().count() == 0); 169 assertNull(exception.get()); 170 } 171 172 /** 173 * Test ThreadFlock::containsThread with nested flocks. 174 */ 175 @ParameterizedTest 176 @MethodSource("factories") 177 void testContainsThread1(ThreadFactory factory) { 178 CountDownLatch latch = new CountDownLatch(1); 179 var exception = new AtomicReference<Exception>(); 180 181 Runnable awaitLatch = () -> { 182 try { 183 latch.await(); 184 } catch (Exception e) { 185 exception.compareAndSet(null, e); 186 } 187 }; 188 189 try (var flock1 = ThreadFlock.open(null)) { 190 var flock2 = ThreadFlock.open(null); 191 try { 192 Thread currentThread = Thread.currentThread(); 193 assertFalse(flock1.containsThread(currentThread)); 194 assertFalse(flock2.containsThread(currentThread)); 195 196 // start thread1 in flock1 197 Thread thread1 = factory.newThread(awaitLatch); 198 flock1.start(thread1); 199 200 // start thread2 in flock2 201 Thread thread2 = factory.newThread(awaitLatch); 202 flock2.start(thread2); 203 204 assertTrue(flock1.containsThread(thread1)); 205 assertTrue(flock1.containsThread(thread2)); 206 assertFalse(flock2.containsThread(thread1)); 207 assertTrue(flock2.containsThread(thread2)); 208 209 } finally { 210 latch.countDown(); // release threads 211 flock2.close(); 212 } 213 } 214 } 215 216 /** 217 * Test ThreadFlock::containsThread with a tree of flocks. 218 */ 219 @ParameterizedTest 220 @MethodSource("factories") 221 void testContainsThread2(ThreadFactory factory) throws Exception { 222 CountDownLatch latch = new CountDownLatch(1); 223 var exception = new AtomicReference<Exception>(); 224 225 Runnable awaitLatch = () -> { 226 try { 227 latch.await(); 228 } catch (Exception e) { 229 exception.compareAndSet(null, e); 230 } 231 }; 232 233 try (var flock1 = ThreadFlock.open(null)) { 234 235 // use box to publish to enclosing scope 236 class Box { 237 volatile ThreadFlock flock2; 238 volatile Thread thread2; 239 } 240 var box = new Box(); 241 242 // flock1 will be "parent" of flock2 243 Thread thread1 = factory.newThread(() -> { 244 try (var flock2 = ThreadFlock.open(null)) { 245 Thread thread2 = factory.newThread(awaitLatch); 246 flock2.start(thread2); 247 box.flock2 = flock2; 248 box.thread2 = thread2; 249 } 250 }); 251 flock1.start(thread1); 252 253 // wait for thread2 to start 254 ThreadFlock flock2; 255 Thread thread2; 256 while ((flock2 = box.flock2) == null || (thread2 = box.thread2) == null) { 257 Thread.sleep(20); 258 } 259 260 try { 261 assertTrue(flock1.containsThread(thread1)); 262 assertTrue(flock1.containsThread(thread2)); 263 assertFalse(flock2.containsThread(thread1)); 264 assertTrue(flock2.containsThread(thread2)); 265 } finally { 266 latch.countDown(); // release threads 267 } 268 } 269 } 270 271 /** 272 * Test that start causes a thread to execute. 273 */ 274 @ParameterizedTest 275 @MethodSource("factories") 276 void testStart(ThreadFactory factory) throws Exception { 277 try (var flock = ThreadFlock.open(null)) { 278 AtomicBoolean executed = new AtomicBoolean(); 279 Thread thread = factory.newThread(() -> executed.set(true)); 280 assertTrue(flock.start(thread) == thread); 281 thread.join(); 282 assertTrue(executed.get()); 283 } 284 } 285 286 /** 287 * Test that start throws IllegalStateException when shutdown 288 */ 289 @ParameterizedTest 290 @MethodSource("factories") 291 void testStartAfterShutdown(ThreadFactory factory) { 292 try (var flock = ThreadFlock.open(null)) { 293 flock.shutdown(); 294 Thread thread = factory.newThread(() -> { }); 295 assertThrows(IllegalStateException.class, () -> flock.start(thread)); 296 } 297 } 298 299 /** 300 * Test that start throws IllegalStateException when closed 301 */ 302 @ParameterizedTest 303 @MethodSource("factories") 304 void testStartAfterClose(ThreadFactory factory) { 305 var flock = ThreadFlock.open(null); 306 flock.close();; 307 Thread thread = factory.newThread(() -> { }); 308 assertThrows(IllegalStateException.class, () -> flock.start(thread)); 309 } 310 311 /** 312 * Test that start throws IllegalThreadStateException when invoked to 313 * start a thread that has already started. 314 */ 315 @ParameterizedTest 316 @MethodSource("factories") 317 void testStartAfterStarted(ThreadFactory factory) { 318 try (var flock = ThreadFlock.open(null)) { 319 Thread thread = factory.newThread(() -> { }); 320 flock.start(thread); 321 assertThrows(IllegalThreadStateException.class, () -> flock.start(thread)); 322 } 323 } 324 325 /** 326 * Test start is confined to threads in the flock. 327 */ 328 @ParameterizedTest 329 @MethodSource("factories") 330 void testStartConfined(ThreadFactory factory) throws Exception { 331 try (var flock = ThreadFlock.open(null)) { 332 // thread in flock 333 testStartConfined(flock, task -> { 334 Thread thread = factory.newThread(task); 335 return flock.start(thread); 336 }); 337 338 // thread in flock 339 try (var flock2 = ThreadFlock.open(null)) { 340 testStartConfined(flock, task -> { 341 Thread thread = factory.newThread(task); 342 return flock2.start(thread); 343 }); 344 } 345 346 // thread not contained in flock 347 testStartConfined(flock, task -> { 348 Thread thread = factory.newThread(task); 349 thread.start(); 350 return thread; 351 }); 352 } 353 } 354 355 /** 356 * Test that a thread created with the given factory cannot start a thread 357 * in the given flock. 358 */ 359 private void testStartConfined(ThreadFlock flock, 360 Function<Runnable, Thread> factory) throws Exception { 361 var exception = new AtomicReference<Exception>(); 362 Thread thread = factory.apply(() -> { 363 try { 364 Thread t = Thread.ofVirtual().unstarted(() -> { }); 365 flock.start(t); 366 } catch (Exception e) { 367 exception.set(e); 368 } 369 }); 370 thread.join(); 371 Throwable cause = exception.get(); 372 if (flock.containsThread(thread)) { 373 assertNull(cause); 374 } else { 375 assertTrue(cause instanceof WrongThreadException); 376 } 377 } 378 379 /** 380 * Test awaitAll with no threads. 381 */ 382 @Test 383 void testAwaitAllWithNoThreads() throws Exception { 384 try (var flock = ThreadFlock.open(null)) { 385 assertTrue(flock.awaitAll()); 386 assertTrue(flock.awaitAll(Duration.ofSeconds(1))); 387 } 388 } 389 390 /** 391 * Test awaitAll with threads running. 392 */ 393 @ParameterizedTest 394 @MethodSource("factories") 395 void testAwaitAllWithThreads(ThreadFactory factory) throws Exception { 396 try (var flock = ThreadFlock.open(null)) { 397 AtomicBoolean done = new AtomicBoolean(); 398 Runnable task = () -> { 399 try { 400 Thread.sleep(Duration.ofMillis(50)); 401 done.set(true); 402 } catch (InterruptedException e) { } 403 }; 404 Thread thread = factory.newThread(task); 405 flock.start(thread); 406 assertTrue(flock.awaitAll()); 407 assertTrue(done.get()); 408 } 409 } 410 411 /** 412 * Test awaitAll with timeout, threads finish before timeout expires. 413 */ 414 @ParameterizedTest 415 @MethodSource("factories") 416 void testAwaitAllWithTimeout1(ThreadFactory factory) throws Exception { 417 try (var flock = ThreadFlock.open(null)) { 418 Runnable task = () -> { 419 try { 420 Thread.sleep(Duration.ofSeconds(2)); 421 } catch (InterruptedException e) { } 422 }; 423 Thread thread = factory.newThread(task); 424 flock.start(thread); 425 426 long startMillis = millisTime(); 427 boolean done = flock.awaitAll(Duration.ofSeconds(30)); 428 assertTrue(done); 429 checkDuration(startMillis, 1900, 20_000); 430 } 431 } 432 433 /** 434 * Test awaitAll with timeout, timeout expires before threads finish. 435 */ 436 @ParameterizedTest 437 @MethodSource("factories") 438 void testAwaitAllWithTimeout2(ThreadFactory factory) throws Exception { 439 try (var flock = ThreadFlock.open(null)) { 440 var latch = new CountDownLatch(1); 441 442 Runnable task = () -> { 443 try { 444 latch.await(); 445 } catch (InterruptedException e) { } 446 }; 447 Thread thread = factory.newThread(task); 448 flock.start(thread); 449 450 try { 451 long startMillis = millisTime(); 452 try { 453 flock.awaitAll(Duration.ofSeconds(2)); 454 fail("awaitAll did not throw"); 455 } catch (TimeoutException e) { 456 checkDuration(startMillis, 1900, 20_000); 457 } 458 } finally { 459 latch.countDown(); 460 } 461 } 462 } 463 464 /** 465 * Test awaitAll with timeout many times. 466 */ 467 @ParameterizedTest 468 @MethodSource("factories") 469 void testAwaitAllWithTimeout3(ThreadFactory factory) throws Exception { 470 try (var flock = ThreadFlock.open(null)) { 471 var latch = new CountDownLatch(1); 472 473 Runnable task = () -> { 474 try { 475 latch.await(); 476 } catch (InterruptedException e) { } 477 }; 478 Thread thread = factory.newThread(task); 479 flock.start(thread); 480 481 try { 482 for (int i = 0; i < 3; i++) { 483 try { 484 flock.awaitAll(Duration.ofMillis(50)); 485 fail("awaitAll did not throw"); 486 } catch (TimeoutException expected) { } 487 } 488 } finally { 489 latch.countDown(); 490 } 491 492 boolean done = flock.awaitAll(); 493 assertTrue(done); 494 } 495 } 496 497 /** 498 * Test awaitAll with a 0 or negative timeout. 499 */ 500 @ParameterizedTest 501 @MethodSource("factories") 502 void testAwaitAllWithTimeout4(ThreadFactory factory) throws Exception { 503 try (var flock = ThreadFlock.open(null)) { 504 var latch = new CountDownLatch(1); 505 506 Runnable task = () -> { 507 try { 508 latch.await(); 509 } catch (InterruptedException e) { } 510 }; 511 Thread thread = factory.newThread(task); 512 flock.start(thread); 513 514 try { 515 try { 516 flock.awaitAll(Duration.ofSeconds(0)); 517 fail("awaitAll did not throw"); 518 } catch (TimeoutException expected) { } 519 try { 520 flock.awaitAll(Duration.ofSeconds(-1)); 521 fail("awaitAll did not throw"); 522 } catch (TimeoutException expected) { } 523 } finally { 524 latch.countDown(); 525 } 526 527 boolean done = flock.awaitAll(); 528 assertTrue(done); 529 } 530 } 531 532 /** 533 * Test awaitAll with interrupt status set, should interrupt thread. 534 */ 535 @ParameterizedTest 536 @MethodSource("factories") 537 void testInterruptAwaitAll1(ThreadFactory factory) { 538 CountDownLatch latch = new CountDownLatch(1); 539 var exception = new AtomicReference<Exception>(); 540 Runnable awaitLatch = () -> { 541 try { 542 latch.await(); 543 } catch (Exception e) { 544 exception.compareAndSet(null, e); 545 } 546 }; 547 548 var flock = ThreadFlock.open(null); 549 try { 550 Thread thread = factory.newThread(awaitLatch); 551 flock.start(thread); 552 553 // invoke awaitAll with interrupt status set. 554 Thread.currentThread().interrupt(); 555 try { 556 flock.awaitAll(); 557 fail("awaitAll did not throw"); 558 } catch (InterruptedException e) { 559 // interrupt status should be clear 560 assertFalse(Thread.currentThread().isInterrupted()); 561 } 562 563 // invoke awaitAll(Duration) with interrupt status set. 564 Thread.currentThread().interrupt(); 565 try { 566 flock.awaitAll(Duration.ofSeconds(30)); 567 fail("awaitAll did not throw"); 568 } catch (TimeoutException e) { 569 fail("TimeoutException not expected"); 570 } catch (InterruptedException e) { 571 // interrupt status should be clear 572 assertFalse(Thread.currentThread().isInterrupted()); 573 } 574 575 } finally { 576 Thread.interrupted(); // clear interrupt 577 578 latch.countDown(); 579 flock.close(); 580 } 581 582 // thread should not have throw any exception 583 assertNull(exception.get()); 584 } 585 586 /** 587 * Test interrupt of awaitAll. 588 */ 589 @ParameterizedTest 590 @MethodSource("factories") 591 void testInterruptAwaitAll2(ThreadFactory factory) { 592 CountDownLatch latch = new CountDownLatch(1); 593 var exception = new AtomicReference<Exception>(); 594 Runnable awaitLatch = () -> { 595 try { 596 latch.await(); 597 } catch (Exception e) { 598 exception.compareAndSet(null, e); 599 } 600 }; 601 602 var flock = ThreadFlock.open(null); 603 try { 604 Thread thread = factory.newThread(awaitLatch); 605 flock.start(thread); 606 607 scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); 608 try { 609 flock.awaitAll(); 610 fail("awaitAll did not throw"); 611 } catch (InterruptedException e) { 612 // interrupt status should be clear 613 assertFalse(Thread.currentThread().isInterrupted()); 614 } 615 616 scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); 617 try { 618 flock.awaitAll(Duration.ofSeconds(30)); 619 fail("awaitAll did not throw"); 620 } catch (TimeoutException e) { 621 fail("TimeoutException not expected"); 622 } catch (InterruptedException e) { 623 // interrupt status should be clear 624 assertFalse(Thread.currentThread().isInterrupted()); 625 } 626 627 } finally { 628 Thread.interrupted(); // clear interrupt 629 630 latch.countDown(); 631 flock.close(); 632 } 633 634 // thread should not have throw any exception 635 assertNull(exception.get()); 636 } 637 638 /** 639 * Test awaitAll after close. 640 */ 641 @Test 642 void testAwaitAfterClose() throws Exception { 643 var flock = ThreadFlock.open(null); 644 flock.close(); 645 assertTrue(flock.awaitAll()); 646 assertTrue(flock.awaitAll(Duration.ofSeconds(1))); 647 } 648 649 /** 650 * Test awaitAll is flock confined. 651 */ 652 @ParameterizedTest 653 @MethodSource("factories") 654 void testAwaitAllConfined(ThreadFactory factory) throws Exception { 655 try (var flock = ThreadFlock.open(null)) { 656 // thread in flock 657 testAwaitAllConfined(flock, task -> { 658 Thread thread = factory.newThread(task); 659 return flock.start(thread); 660 }); 661 662 // thread not in flock 663 testAwaitAllConfined(flock, task -> { 664 Thread thread = factory.newThread(task); 665 thread.start(); 666 return thread; 667 }); 668 } 669 } 670 671 /** 672 * Test that a thread created with the given factory cannot call awaitAll. 673 */ 674 private void testAwaitAllConfined(ThreadFlock flock, 675 Function<Runnable, Thread> factory) throws Exception { 676 var exception = new AtomicReference<Exception>(); 677 Thread thread = factory.apply(() -> { 678 try { 679 flock.awaitAll(); 680 flock.awaitAll(Duration.ofMillis(1)); 681 } catch (Exception e) { 682 exception.set(e); 683 } 684 }); 685 thread.join(); 686 Throwable cause = exception.get(); 687 assertTrue(cause instanceof WrongThreadException); 688 } 689 690 /** 691 * Test awaitAll with the wakeup permit. 692 */ 693 @ParameterizedTest 694 @MethodSource("factories") 695 void testWakeupAwaitAll1(ThreadFactory factory) throws Exception { 696 try (var flock = ThreadFlock.open(null)) { 697 CountDownLatch latch = new CountDownLatch(1); 698 var exception = new AtomicReference<Exception>(); 699 Runnable task = () -> { 700 try { 701 latch.await(); 702 } catch (Exception e) { 703 exception.compareAndSet(null, e); 704 } 705 }; 706 Thread thread = factory.newThread(task); 707 flock.start(thread); 708 709 // invoke awaitAll with permit 710 try { 711 flock.wakeup(); 712 assertFalse(flock.awaitAll()); 713 } finally { 714 latch.countDown(); 715 } 716 } 717 } 718 719 /** 720 * Schedule a thread to wakeup the owner waiting in awaitAll. 721 */ 722 @ParameterizedTest 723 @MethodSource("factories") 724 void testWakeupAwaitAll2(ThreadFactory factory) throws Exception { 725 try (var flock = ThreadFlock.open(null)) { 726 CountDownLatch latch = new CountDownLatch(1); 727 var exception = new AtomicReference<Exception>(); 728 Runnable task = () -> { 729 try { 730 latch.await(); 731 } catch (Exception e) { 732 exception.compareAndSet(null, e); 733 } 734 }; 735 Thread thread1 = factory.newThread(task); 736 flock.start(thread1); 737 738 // schedule thread to invoke wakeup 739 Thread thread2 = factory.newThread(() -> { 740 try { Thread.sleep(Duration.ofMillis(500)); } catch (Exception e) { } 741 flock.wakeup(); 742 }); 743 flock.start(thread2); 744 745 try { 746 assertFalse(flock.awaitAll()); 747 } finally { 748 latch.countDown(); 749 } 750 } 751 } 752 753 /** 754 * Test close with no threads running. 755 */ 756 @Test 757 void testCloseWithNoThreads() { 758 var flock = ThreadFlock.open(null); 759 flock.close(); 760 assertTrue(flock.isClosed()); 761 assertTrue(flock.threads().count() == 0); 762 } 763 764 /** 765 * Test close with threads running. 766 */ 767 @ParameterizedTest 768 @MethodSource("factories") 769 void testCloseWithThreads(ThreadFactory factory) { 770 var exception = new AtomicReference<Exception>(); 771 Runnable sleepTask = () -> { 772 try { 773 Thread.sleep(Duration.ofMillis(50)); 774 } catch (Exception e) { 775 exception.set(e); 776 } 777 }; 778 var flock = ThreadFlock.open(null); 779 try { 780 Thread thread = factory.newThread(sleepTask); 781 flock.start(thread); 782 } finally { 783 flock.close(); 784 } 785 assertTrue(flock.isClosed()); 786 assertTrue(flock.threads().count() == 0); 787 assertNull(exception.get()); // no exception thrown 788 } 789 790 /** 791 * Test close after flock is closed. 792 */ 793 @Test 794 void testCloseAfterClose() { 795 var flock = ThreadFlock.open(null); 796 flock.close(); 797 assertTrue(flock.isClosed()); 798 flock.close(); 799 assertTrue(flock.isClosed()); 800 } 801 802 /** 803 * Test close is owner confined. 804 */ 805 @ParameterizedTest 806 @MethodSource("factories") 807 void testCloseConfined(ThreadFactory factory) throws Exception { 808 try (var flock = ThreadFlock.open(null)) { 809 // thread in flock 810 testCloseConfined(flock, task -> { 811 Thread thread = factory.newThread(task); 812 return flock.start(thread); 813 }); 814 815 // thread not in flock 816 testCloseConfined(flock, task -> { 817 Thread thread = factory.newThread(task); 818 thread.start(); 819 return thread; 820 }); 821 } 822 } 823 824 /** 825 * Test that a thread created with the given factory cannot close the 826 * given flock. 827 */ 828 private void testCloseConfined(ThreadFlock flock, 829 Function<Runnable, Thread> factory) throws Exception { 830 var exception = new AtomicReference<Exception>(); 831 Thread thread = factory.apply(() -> { 832 try { 833 flock.close(); 834 } catch (Exception e) { 835 exception.set(e); 836 } 837 }); 838 thread.join(); 839 Throwable cause = exception.get(); 840 assertTrue(cause instanceof WrongThreadException); 841 } 842 843 /** 844 * Test close with interrupt status set, should not interrupt threads. 845 */ 846 @ParameterizedTest 847 @MethodSource("factories") 848 void testInterruptClose1(ThreadFactory factory) { 849 var exception = new AtomicReference<Exception>(); 850 Runnable sleepTask = () -> { 851 try { 852 Thread.sleep(Duration.ofSeconds(1)); 853 } catch (Exception e) { 854 exception.set(e); 855 } 856 }; 857 try (var flock = ThreadFlock.open(null)) { 858 Thread thread = factory.newThread(sleepTask); 859 flock.start(thread); 860 Thread.currentThread().interrupt(); 861 } finally { 862 assertTrue(Thread.interrupted()); // clear interrupt 863 } 864 assertNull(exception.get()); 865 } 866 867 /** 868 * Test interrupt thread block in close. 869 */ 870 @ParameterizedTest 871 @MethodSource("factories") 872 void testInterruptClose2(ThreadFactory factory) { 873 var exception = new AtomicReference<Exception>(); 874 Runnable sleepTask = () -> { 875 try { 876 Thread.sleep(Duration.ofSeconds(5)); 877 } catch (Exception e) { 878 exception.set(e); 879 } 880 }; 881 try (var flock = ThreadFlock.open(null)) { 882 Thread thread = factory.newThread(sleepTask); 883 flock.start(thread); 884 scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); 885 } finally { 886 assertTrue(Thread.interrupted()); // clear interrupt 887 } 888 assertNull(exception.get()); 889 } 890 891 /** 892 * Test that closing an enclosing thread flock closes a nested thread flocks. 893 */ 894 @Test 895 void testStructureViolation() { 896 try (var flock1 = ThreadFlock.open("flock1")) { 897 try (var flock2 = ThreadFlock.open("flock2")) { 898 try { 899 flock1.close(); 900 fail("close did not throw"); 901 } catch (RuntimeException e) { 902 assertTrue(e.toString().contains("Structure")); 903 } 904 assertTrue(flock1.isClosed()); 905 assertTrue(flock2.isClosed()); 906 } 907 } 908 } 909 910 /** 911 * Test Thread exiting with an open flock. The exiting thread should close the flock. 912 */ 913 @ParameterizedTest 914 @MethodSource("factories") 915 void testThreadExitWithOpenFlock(ThreadFactory factory) throws Exception { 916 var flockRef = new AtomicReference<ThreadFlock>(); 917 var childRef = new AtomicReference<Thread>(); 918 919 Thread thread = factory.newThread(() -> { 920 Thread.dumpStack(); 921 922 var flock = ThreadFlock.open(null); 923 Thread child = factory.newThread(() -> { 924 try { 925 Thread.sleep(Duration.ofSeconds(2)); 926 } catch (InterruptedException e) { } 927 }); 928 flock.start(child); 929 flockRef.set(flock); 930 childRef.set(child); 931 }); 932 thread.start(); 933 thread.join(); 934 935 // flock should be closed and the child thread should have terminated 936 ThreadFlock flock = flockRef.get(); 937 Thread child = childRef.get(); 938 assertTrue(flock.isClosed() && child.join(Duration.ofMillis(500))); 939 } 940 941 /** 942 * Test toString includes the flock name. 943 */ 944 @Test 945 void testToString() { 946 try (var flock = ThreadFlock.open("xxxx")) { 947 assertTrue(flock.toString().contains("xxxx")); 948 } 949 } 950 951 /** 952 * Test for NullPointerException. 953 */ 954 @Test 955 void testNulls() { 956 try (var flock = ThreadFlock.open(null)) { 957 assertThrows(NullPointerException.class, () -> flock.start(null)); 958 assertThrows(NullPointerException.class, () -> flock.awaitAll(null)); 959 assertThrows(NullPointerException.class, () -> flock.containsThread(null)); 960 } 961 } 962 963 /** 964 * Schedules a thread to be interrupted after the given delay. 965 */ 966 private void scheduleInterrupt(Thread thread, Duration delay) { 967 long millis = delay.toMillis(); 968 scheduler.schedule(thread::interrupt, millis, TimeUnit.MILLISECONDS); 969 } 970 971 /** 972 * Returns the current time in milliseconds. 973 */ 974 private static long millisTime() { 975 long now = System.nanoTime(); 976 return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); 977 } 978 979 /** 980 * Check the duration of a task 981 * @param start start time, in milliseconds 982 * @param min minimum expected duration, in milliseconds 983 * @param max maximum expected duration, in milliseconds 984 * @return the duration (now - start), in milliseconds 985 */ 986 private static long checkDuration(long start, long min, long max) { 987 long duration = millisTime() - start; 988 assertTrue(duration >= min, 989 "Duration " + duration + "ms, expected >= " + min + "ms"); 990 assertTrue(duration <= max, 991 "Duration " + duration + "ms, expected <= " + max + "ms"); 992 return duration; 993 } 994 }