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("xxxx"));
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 }