1 /* 2 * Copyright (c) 2021, 2023, 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, 4000); 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, 4000); 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 wakeup is flock confined. 755 */ 756 @ParameterizedTest 757 @MethodSource("factories") 758 void testWakeupConfined(ThreadFactory factory) throws Exception { 759 try (var flock = ThreadFlock.open(null)) { 760 // thread in flock 761 testWakeupConfined(flock, task -> { 762 Thread thread = factory.newThread(task); 763 return flock.start(thread); 764 }); 765 766 // thread not in flock 767 testWakeupConfined(flock, task -> { 768 Thread thread = factory.newThread(task); 769 thread.start(); 770 return thread; 771 }); 772 } 773 } 774 775 /** 776 * Test that a thread created with the given factory cannot wakeup the 777 * given flock. 778 */ 779 private void testWakeupConfined(ThreadFlock flock, 780 Function<Runnable, Thread> factory) throws Exception { 781 var exception = new AtomicReference<Exception>(); 782 Thread thread = factory.apply(() -> { 783 try { 784 flock.wakeup(); 785 } catch (Exception e) { 786 exception.set(e); 787 } 788 }); 789 thread.join(); 790 Throwable cause = exception.get(); 791 if (flock.containsThread(thread)) { 792 assertNull(cause); 793 } else { 794 assertTrue(cause instanceof WrongThreadException); 795 } 796 } 797 798 /** 799 * Test close with no threads running. 800 */ 801 @Test 802 void testCloseWithNoThreads() { 803 var flock = ThreadFlock.open(null); 804 flock.close(); 805 assertTrue(flock.isClosed()); 806 assertTrue(flock.threads().count() == 0); 807 } 808 809 /** 810 * Test close with threads running. 811 */ 812 @ParameterizedTest 813 @MethodSource("factories") 814 void testCloseWithThreads(ThreadFactory factory) { 815 var exception = new AtomicReference<Exception>(); 816 Runnable sleepTask = () -> { 817 try { 818 Thread.sleep(Duration.ofMillis(50)); 819 } catch (Exception e) { 820 exception.set(e); 821 } 822 }; 823 var flock = ThreadFlock.open(null); 824 try { 825 Thread thread = factory.newThread(sleepTask); 826 flock.start(thread); 827 } finally { 828 flock.close(); 829 } 830 assertTrue(flock.isClosed()); 831 assertTrue(flock.threads().count() == 0); 832 assertNull(exception.get()); // no exception thrown 833 } 834 835 /** 836 * Test close after flock is closed. 837 */ 838 @Test 839 void testCloseAfterClose() { 840 var flock = ThreadFlock.open(null); 841 flock.close(); 842 assertTrue(flock.isClosed()); 843 flock.close(); 844 assertTrue(flock.isClosed()); 845 } 846 847 /** 848 * Test close is owner confined. 849 */ 850 @ParameterizedTest 851 @MethodSource("factories") 852 void testCloseConfined(ThreadFactory factory) throws Exception { 853 try (var flock = ThreadFlock.open(null)) { 854 // thread in flock 855 testCloseConfined(flock, task -> { 856 Thread thread = factory.newThread(task); 857 return flock.start(thread); 858 }); 859 860 // thread not in flock 861 testCloseConfined(flock, task -> { 862 Thread thread = factory.newThread(task); 863 thread.start(); 864 return thread; 865 }); 866 } 867 } 868 869 /** 870 * Test that a thread created with the given factory cannot close the 871 * given flock. 872 */ 873 private void testCloseConfined(ThreadFlock flock, 874 Function<Runnable, Thread> factory) throws Exception { 875 var exception = new AtomicReference<Exception>(); 876 Thread thread = factory.apply(() -> { 877 try { 878 flock.close(); 879 } catch (Exception e) { 880 exception.set(e); 881 } 882 }); 883 thread.join(); 884 Throwable cause = exception.get(); 885 assertTrue(cause instanceof WrongThreadException); 886 } 887 888 /** 889 * Test close with interrupt status set, should not interrupt threads. 890 */ 891 @ParameterizedTest 892 @MethodSource("factories") 893 void testInterruptClose1(ThreadFactory factory) { 894 var exception = new AtomicReference<Exception>(); 895 Runnable sleepTask = () -> { 896 try { 897 Thread.sleep(Duration.ofSeconds(1)); 898 } catch (Exception e) { 899 exception.set(e); 900 } 901 }; 902 try (var flock = ThreadFlock.open(null)) { 903 Thread thread = factory.newThread(sleepTask); 904 flock.start(thread); 905 Thread.currentThread().interrupt(); 906 } finally { 907 assertTrue(Thread.interrupted()); // clear interrupt 908 } 909 assertNull(exception.get()); 910 } 911 912 /** 913 * Test interrupt thread block in close. 914 */ 915 @ParameterizedTest 916 @MethodSource("factories") 917 void testInterruptClose2(ThreadFactory factory) { 918 var exception = new AtomicReference<Exception>(); 919 Runnable sleepTask = () -> { 920 try { 921 Thread.sleep(Duration.ofSeconds(5)); 922 } catch (Exception e) { 923 exception.set(e); 924 } 925 }; 926 try (var flock = ThreadFlock.open(null)) { 927 Thread thread = factory.newThread(sleepTask); 928 flock.start(thread); 929 scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); 930 } finally { 931 assertTrue(Thread.interrupted()); // clear interrupt 932 } 933 assertNull(exception.get()); 934 } 935 936 /** 937 * Test shutdown is confined to threads in the flock. 938 */ 939 @ParameterizedTest 940 @MethodSource("factories") 941 void testShutdownConfined(ThreadFactory factory) throws Exception { 942 try (var flock = ThreadFlock.open(null)) { 943 // thread in flock 944 testShutdownConfined(flock, task -> { 945 Thread thread = factory.newThread(task); 946 return flock.start(thread); 947 }); 948 949 // thread in flock 950 try (var flock2 = ThreadFlock.open(null)) { 951 testShutdownConfined(flock, task -> { 952 Thread thread = factory.newThread(task); 953 return flock2.start(thread); 954 }); 955 } 956 957 // thread not contained in flock 958 testShutdownConfined(flock, task -> { 959 Thread thread = factory.newThread(task); 960 thread.start(); 961 return thread; 962 }); 963 } 964 } 965 966 /** 967 * Test that a thread created with the given factory cannot shut down the 968 * given flock. 969 */ 970 private void testShutdownConfined(ThreadFlock flock, 971 Function<Runnable, Thread> factory) throws Exception { 972 var exception = new AtomicReference<Exception>(); 973 Thread thread = factory.apply(() -> { 974 try { 975 flock.shutdown(); 976 } catch (Exception e) { 977 exception.set(e); 978 } 979 }); 980 thread.join(); 981 Throwable cause = exception.get(); 982 if (flock.containsThread(thread)) { 983 assertNull(cause); 984 } else { 985 assertTrue(cause instanceof WrongThreadException); 986 } 987 } 988 989 /** 990 * Test that closing an enclosing thread flock closes a nested thread flocks. 991 */ 992 @Test 993 void testStructureViolation() { 994 try (var flock1 = ThreadFlock.open("flock1")) { 995 try (var flock2 = ThreadFlock.open("flock2")) { 996 try { 997 flock1.close(); 998 fail("close did not throw"); 999 } catch (RuntimeException e) { 1000 assertTrue(e.toString().contains("Structure")); 1001 } 1002 assertTrue(flock1.isClosed()); 1003 assertTrue(flock2.isClosed()); 1004 } 1005 } 1006 } 1007 1008 /** 1009 * Test Thread exiting with an open flock. The exiting thread should close the flock. 1010 */ 1011 @ParameterizedTest 1012 @MethodSource("factories") 1013 void testThreadExitWithOpenFlock(ThreadFactory factory) throws Exception { 1014 var flockRef = new AtomicReference<ThreadFlock>(); 1015 var childRef = new AtomicReference<Thread>(); 1016 1017 Thread thread = factory.newThread(() -> { 1018 Thread.dumpStack(); 1019 1020 var flock = ThreadFlock.open(null); 1021 Thread child = factory.newThread(() -> { 1022 try { 1023 Thread.sleep(Duration.ofSeconds(2)); 1024 } catch (InterruptedException e) { } 1025 }); 1026 flock.start(child); 1027 flockRef.set(flock); 1028 childRef.set(child); 1029 }); 1030 thread.start(); 1031 thread.join(); 1032 1033 // flock should be closed and the child thread should have terminated 1034 ThreadFlock flock = flockRef.get(); 1035 Thread child = childRef.get(); 1036 assertTrue(flock.isClosed() && child.join(Duration.ofMillis(500))); 1037 } 1038 1039 /** 1040 * Test toString includes the flock name. 1041 */ 1042 @Test 1043 void testToString() { 1044 try (var flock = ThreadFlock.open("xxxx")) { 1045 assertTrue(flock.toString().contains("xxx")); 1046 } 1047 } 1048 1049 /** 1050 * Test for NullPointerException. 1051 */ 1052 @Test 1053 void testNulls() { 1054 try (var flock = ThreadFlock.open(null)) { 1055 assertThrows(NullPointerException.class, () -> flock.start(null)); 1056 assertThrows(NullPointerException.class, () -> flock.awaitAll(null)); 1057 assertThrows(NullPointerException.class, () -> flock.containsThread(null)); 1058 } 1059 } 1060 1061 /** 1062 * Schedules a thread to be interrupted after the given delay. 1063 */ 1064 private void scheduleInterrupt(Thread thread, Duration delay) { 1065 long millis = delay.toMillis(); 1066 scheduler.schedule(thread::interrupt, millis, TimeUnit.MILLISECONDS); 1067 } 1068 1069 /** 1070 * Returns the current time in milliseconds. 1071 */ 1072 private static long millisTime() { 1073 long now = System.nanoTime(); 1074 return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); 1075 } 1076 1077 /** 1078 * Check the duration of a task 1079 * @param start start time, in milliseconds 1080 * @param min minimum expected duration, in milliseconds 1081 * @param max maximum expected duration, in milliseconds 1082 * @return the duration (now - start), in milliseconds 1083 */ 1084 private static long checkDuration(long start, long min, long max) { 1085 long duration = millisTime() - start; 1086 assertTrue(duration >= min, 1087 "Duration " + duration + "ms, expected >= " + min + "ms"); 1088 assertTrue(duration <= max, 1089 "Duration " + duration + "ms, expected <= " + max + "ms"); 1090 return duration; 1091 } 1092 }