1 /*
   2  * Copyright (c) 2018, 2019, 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
  26  * @run testng Basic
  27  * @summary Basic tests for java.lang.Fiber
  28  */
  29 
  30 import java.lang.ref.WeakReference;
  31 import java.lang.reflect.Method;
  32 import java.time.Duration;
  33 import java.util.List;
  34 import java.util.concurrent.*;
  35 import java.util.concurrent.atomic.*;
  36 import java.util.concurrent.locks.*;
  37 import java.util.stream.Stream;
  38 import java.nio.channels.Selector;
  39 
  40 import org.testng.annotations.Test;
  41 import org.testng.annotations.DataProvider;
  42 import static org.testng.Assert.*;
  43 
  44 @Test
  45 public class Basic {
  46 
  47     static final Runnable DO_NOTHING = () -> { };
  48 
  49     // -- basic tests ---
  50 
  51     public void testExecute1() throws Exception {
  52         var executed = new AtomicBoolean();
  53         FiberScope.background().schedule(() -> executed.set(true)).join();
  54         assertTrue(executed.get());
  55     }
  56 
  57     public void testExecute2() throws Exception {
  58         String s = FiberScope.background().schedule(() -> "foo").join();
  59         assertTrue("foo".equals(s));
  60     }
  61 
  62     // throw uncaught exception
  63     public void testUncaughtException1() throws Exception {
  64         class FooException extends RuntimeException { }
  65         var fiber = FiberScope.background().schedule(() -> {
  66             throw new FooException();
  67         });
  68         try {
  69             fiber.join();
  70             assertTrue(false);
  71         } catch (CompletionException e) {
  72             assertTrue(e.getCause() instanceof FooException);
  73         }
  74     }
  75 
  76     // throw uncaught error
  77     public void testUncaughtError1() throws Exception {
  78         class FooError extends Error { }
  79         var fiber = FiberScope.background().schedule(() -> {
  80             throw new FooError();
  81         });
  82         try {
  83             fiber.join();
  84             assertTrue(false);
  85         } catch (CompletionException e) {
  86             assertTrue(e.getCause() instanceof FooError);
  87         }
  88     }
  89 
  90 
  91     // -- parking --
  92 
  93     // fiber parks, unparked by thread
  94     public void testPark1() throws Exception {
  95         var fiber = FiberScope.background().schedule(() -> LockSupport.park());
  96         Thread.sleep(1000); // give time for fiber to park
  97         LockSupport.unpark(fiber);
  98         fiber.join();
  99     }
 100 
 101     // fiber parks, unparked by another fiber
 102     public void testPark2() throws Exception {
 103         var fiber1 = FiberScope.background().schedule(() -> LockSupport.park());
 104         Thread.sleep(1000); // give time for fiber to park
 105         var fiber2 = FiberScope.background().schedule(() -> LockSupport.unpark(fiber1));
 106         fiber1.join();
 107         fiber2.join();
 108     }
 109 
 110     // park while holding monitor
 111     public void testPark3() throws Exception {
 112         var fiber = FiberScope.background().schedule(() -> {
 113             var lock = new Object();
 114             synchronized (lock) {
 115                 LockSupport.park();
 116             }
 117         });
 118         Thread.sleep(1000); // give time for fiber to park
 119         LockSupport.unpark(fiber);
 120         fiber.join();
 121     }
 122 
 123     // park with native frame on the stack
 124     public void testPark4() throws Exception {
 125         var fiber = FiberScope.background().schedule(() -> {
 126             try {
 127                 Method m = Basic.class.getDeclaredMethod("doPark");
 128                 m.invoke(null);
 129             } catch (Exception e) {
 130                 assertTrue(false);
 131             }
 132         });
 133         Thread.sleep(1000); // give time for fiber to park
 134         LockSupport.unpark(fiber);
 135         fiber.join();
 136     }
 137     static void doPark() {
 138         LockSupport.park();
 139     }
 140 
 141     // unpark before park
 142     public void testPark5() throws Exception {
 143         var fiber = FiberScope.background().schedule(() -> {
 144             LockSupport.unpark(Fiber.current().orElseThrow());
 145             LockSupport.park();
 146         });
 147         fiber.join();
 148     }
 149 
 150     // 2 x unpark before park
 151     public void testPark6() throws Exception {
 152         var fiber = FiberScope.background().schedule(() -> {
 153             Fiber me = Fiber.current().orElseThrow();
 154             LockSupport.unpark(me);
 155             LockSupport.unpark(me);
 156             LockSupport.park();
 157             LockSupport.park();  // should park
 158         });
 159         Thread.sleep(1000); // give time for fiber to park
 160         LockSupport.unpark(fiber);
 161         fiber.join();
 162     }
 163 
 164     // 2 x park
 165     public void testPark7() throws Exception {
 166         var fiber = FiberScope.background().schedule(() -> {
 167             LockSupport.park();
 168             LockSupport.park();
 169         });
 170 
 171         Thread.sleep(1000); // give time for fiber to park
 172 
 173         // unpark, fiber should park again
 174         LockSupport.unpark(fiber);
 175         Thread.sleep(1000);
 176 
 177         // let it terminate
 178         LockSupport.unpark(fiber);
 179         fiber.join();
 180     }
 181 
 182     // interrupt before park
 183     public void testPark8() throws Exception {
 184         var fiber = FiberScope.background().schedule(() -> {
 185             Thread t = Thread.currentThread();
 186             t.interrupt();
 187             LockSupport.park();
 188             assertTrue(t.isInterrupted());
 189         });
 190         fiber.join();
 191     }
 192 
 193     // interrupt while parked
 194     public void testPark9() throws Exception {
 195         var fiber = FiberScope.background().schedule(() -> {
 196             Thread t = Thread.currentThread();
 197             Interrupter.schedule(t, 1000);
 198             LockSupport.park();
 199             assertTrue(t.isInterrupted());
 200         });
 201         fiber.join();
 202     }
 203 
 204     // interrupt before park (pinned park)
 205     public void testPark10() throws Exception {
 206         var fiber = FiberScope.background().schedule(() -> {
 207             Thread t = Thread.currentThread();
 208             t.interrupt();
 209             Object lock = new Object();
 210             synchronized (lock) {
 211                 LockSupport.park();
 212             }
 213             assertTrue(t.isInterrupted());
 214         });
 215         fiber.join();
 216     }
 217 
 218     // interrupt while parked (pinned park)
 219     public void testPark11() throws Exception {
 220         var fiber = FiberScope.background().schedule(() -> {
 221             Thread t = Thread.currentThread();
 222             Interrupter.schedule(t, 1000);
 223             Object lock = new Object();
 224             synchronized (lock) {
 225                 LockSupport.park();
 226             }
 227             assertTrue(t.isInterrupted());
 228         });
 229         fiber.join();
 230     }
 231 
 232     // parkNanos(-1) completes immediately
 233     public void testParkNanos1() throws Exception {
 234         var fiber = FiberScope.background().schedule(() -> LockSupport.parkNanos(-1));
 235         fiber.join();
 236     }
 237 
 238     // parkNanos(0) completes immediately
 239     public void testParkNanos2() throws Exception {
 240         var fiber = FiberScope.background().schedule(() -> LockSupport.parkNanos(0));
 241         fiber.join();
 242     }
 243 
 244     // parkNanos(1000ms) completes quickly
 245     public void testParkNanos3() throws Exception {
 246         var fiber = FiberScope.background().schedule(() -> {
 247             // park for 1000ms
 248             long nanos = TimeUnit.NANOSECONDS.convert(1000, TimeUnit.MILLISECONDS);
 249             long start = System.nanoTime();
 250             LockSupport.parkNanos(nanos);
 251 
 252             // check that fiber parks for >= 900ms
 253             long elapsed = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start,
 254                                                          TimeUnit.NANOSECONDS);
 255             assertTrue(elapsed >= 900);
 256         });
 257         fiber.join();
 258     }
 259 
 260     // fiber parks, unparked by thread
 261     public void testParkNanos4() throws Exception {
 262         var fiber = FiberScope.background().schedule(() -> {
 263             long nanos = TimeUnit.NANOSECONDS.convert(30, TimeUnit.SECONDS);
 264             LockSupport.parkNanos(nanos);
 265         });
 266         Thread.sleep(1000); // give time for fiber to park
 267         LockSupport.unpark(fiber);
 268         fiber.join();
 269     }
 270 
 271     // fiber parks, unparked by another fiber
 272     public void testParkNanos5() throws Exception {
 273         var fiber1 = FiberScope.background().schedule(() -> {
 274             long nanos = TimeUnit.NANOSECONDS.convert(30, TimeUnit.SECONDS);
 275             LockSupport.parkNanos(nanos);
 276         });
 277         Thread.sleep(1000);  // give time for fiber to park
 278         var fiber2 = FiberScope.background().schedule(() -> {
 279             LockSupport.unpark(fiber1);
 280         });
 281         fiber1.join();
 282         fiber2.join();
 283     }
 284 
 285     // unpark before parkNanos
 286     public void testParkNanos6() throws Exception {
 287         var fiber = FiberScope.background().schedule(() -> {
 288             LockSupport.unpark(Fiber.current().orElseThrow());
 289             long nanos = TimeUnit.NANOSECONDS.convert(30, TimeUnit.SECONDS);
 290             LockSupport.parkNanos(nanos);
 291         });
 292         fiber.join();
 293     }
 294 
 295     // unpark before parkNanos(0), should consume permit
 296     public void testParkNanos7() throws Exception {
 297         var fiber = FiberScope.background().schedule(() -> {
 298             LockSupport.unpark(Fiber.current().orElseThrow());
 299             LockSupport.parkNanos(0);
 300             LockSupport.park(); // should block
 301         });
 302         fiber.awaitTermination(Duration.ofSeconds(2));
 303         LockSupport.unpark(fiber);
 304         fiber.join();
 305     }
 306 
 307 
 308     // -- join --
 309 
 310     // join short lived fiber
 311     public void testJoin1() throws Exception {
 312         String s = FiberScope.background().schedule(() -> "foo").join();
 313         assertEquals(s, "foo");
 314     }
 315 
 316     // join long lived fiber
 317     public void testJoin2() throws Exception {
 318         Fiber<String> fiber = FiberScope.background().schedule(() -> {
 319             try {
 320                 Thread.sleep(2*1000);
 321             } catch (InterruptedException e) { }
 322             return "foo";
 323         });
 324         String s = fiber.join();
 325         assertEquals(s, "foo");
 326     }
 327 
 328     // join after terminated
 329     public void testJoin3() throws Exception {
 330         Fiber<String> fiber = FiberScope.background().schedule(() -> "foo");
 331         while (fiber.isAlive()) {
 332             Thread.sleep(10);
 333         }
 334         String s = fiber.join();
 335         assertEquals(s, "foo");
 336     }
 337 
 338     // thread interrupted before join
 339     public void testJoin4() throws Exception {
 340         Fiber<?> fiber = FiberScope.background().schedule(() -> LockSupport.park());
 341         Thread.currentThread().interrupt();
 342         try {
 343             fiber.join();
 344             assertTrue(false);
 345         } catch (InterruptedException e) {
 346             assertFalse(Thread.interrupted());
 347         } finally {
 348             LockSupport.unpark(fiber);
 349         }
 350     }
 351 
 352     // thread interrupted in join
 353     public void testJoin5() throws Exception {
 354         Fiber<?> fiber = FiberScope.background().schedule(() -> LockSupport.park());
 355         Interrupter.schedule(Thread.currentThread(), 500);
 356         try {
 357             fiber.join();
 358             assertTrue(false);
 359         } catch (InterruptedException e) {
 360             assertFalse(Thread.interrupted());
 361         } finally {
 362             LockSupport.unpark(fiber);
 363         }
 364     }
 365 
 366     // fiber interrupted before join
 367     public void testJoin6() throws Exception {
 368         var fiber1 = FiberScope.background().schedule(() -> LockSupport.park());
 369         var fiber2 = FiberScope.background().schedule(() -> {
 370             Thread.currentThread().interrupt();
 371             try {
 372                 fiber1.join();
 373                 assertTrue(false);
 374             } catch (InterruptedException e) {
 375                 assertFalse(Thread.interrupted());
 376             }
 377         });
 378         try {
 379             fiber2.join();
 380         } finally {
 381             LockSupport.unpark(fiber1);
 382         }
 383     }
 384 
 385 
 386     // -- awaitTermination --
 387 
 388 
 389     public void testAwaitTermination1() throws Exception {
 390         var fiber = FiberScope.background().schedule(() -> "foo");
 391         boolean terminated = fiber.awaitTermination(Duration.ofSeconds(10));
 392         assertTrue(terminated);
 393     }
 394 
 395     public void testAwaitTermination2() throws Exception {
 396         var fiber = FiberScope.background().schedule(() -> {
 397             Thread.sleep(1000);
 398             return null;
 399         });
 400         boolean terminated = fiber.awaitTermination(Duration.ofSeconds(10));
 401         assertTrue(terminated);
 402     }
 403 
 404     public void testAwaitTermination3() throws Exception {
 405         var fiber = FiberScope.background().schedule(() -> LockSupport.park());
 406         boolean terminated = fiber.awaitTermination(Duration.ofSeconds(1));
 407         try {
 408             assertFalse(terminated);
 409         } finally {
 410             LockSupport.unpark(fiber);
 411         }
 412     }
 413 
 414     // awaitTermination after terminated
 415     public void testAwaitTermination4() throws Exception {
 416         var fiber = FiberScope.background().schedule(() -> "foo");
 417         fiber.join();
 418         boolean terminated = fiber.awaitTermination(Duration.ofSeconds(10));
 419         assertTrue(terminated);
 420     }
 421 
 422     // thread interrupted before awaitTermination
 423     public void testAwaitTermination5() throws Exception {
 424         Fiber<?> fiber = FiberScope.background().schedule(() -> LockSupport.park());
 425         Thread.currentThread().interrupt();
 426         try {
 427             fiber.awaitTermination(Duration.ofSeconds(10));
 428             assertTrue(false);
 429         } catch (InterruptedException e) {
 430             assertFalse(Thread.interrupted());
 431         } finally {
 432             LockSupport.unpark(fiber);
 433         }
 434     }
 435 
 436     // thread interrupted in awaitTermination
 437     public void testAwaitTermination6() throws Exception {
 438         Fiber<?> fiber = FiberScope.background().schedule(() -> LockSupport.park());
 439         Interrupter.schedule(Thread.currentThread(), 500);
 440         try {
 441             fiber.awaitTermination(Duration.ofSeconds(10));
 442             assertTrue(false);
 443         } catch (InterruptedException e) {
 444             assertFalse(Thread.interrupted());
 445         } finally {
 446             LockSupport.unpark(fiber);
 447         }
 448     }
 449 
 450     // fiber interrupted before awaitTermination
 451     public void testAwaitTermination7() throws Exception {
 452         var fiber1 = FiberScope.background().schedule(() -> LockSupport.park());
 453         var fiber2 = FiberScope.background().schedule(() -> {
 454             Thread.currentThread().interrupt();
 455             try {
 456                 fiber1.awaitTermination(Duration.ofSeconds(10));
 457                 assertTrue(false);
 458             } catch (InterruptedException e) {
 459                 assertFalse(Thread.interrupted());
 460             }
 461         });
 462         try {
 463             fiber2.join();
 464         } finally {
 465             LockSupport.unpark(fiber1);
 466         }
 467     }
 468 
 469     // fiber interrupted in timed awaitTermination
 470     public void testAwaitTermination8() throws Exception {
 471         var fiber1 = FiberScope.background().schedule(() -> LockSupport.park());
 472         var fiber2 = FiberScope.background().schedule(() -> {
 473             Interrupter.schedule(Thread.currentThread(), 500);
 474             try {
 475                 fiber1.awaitTermination(Duration.ofSeconds(10));
 476                 assertTrue(false);
 477             } catch (InterruptedException e) {
 478                 assertFalse(Thread.interrupted());
 479             }
 480         });
 481         try {
 482             fiber2.join();
 483         } finally {
 484             LockSupport.unpark(fiber1);
 485         }
 486     }
 487 
 488     // awaitTermination with zero duration
 489     public void testAwaitTermination9() throws Exception {
 490         var fiber = FiberScope.background().schedule(() -> "foo");
 491         fiber.join();
 492         boolean terminated = fiber.awaitTermination(Duration.ofSeconds(0));
 493         assertTrue(terminated);
 494     }
 495 
 496     public void testAwaitTermination10() throws Exception {
 497         var fiber = FiberScope.background().schedule(() -> LockSupport.park());
 498         boolean terminated = fiber.awaitTermination(Duration.ofSeconds(0));
 499         try {
 500             assertFalse(terminated);
 501         } finally {
 502             LockSupport.unpark(fiber);
 503         }
 504     }
 505 
 506     public void testAwaitTermination11() throws Exception {
 507         var fiber = FiberScope.background().schedule(() -> "foo");
 508         fiber.join();
 509         Thread.currentThread().interrupt();
 510         try {
 511             fiber.awaitTermination(Duration.ofSeconds(0));
 512             assertTrue(false);
 513         } catch (InterruptedException e) {
 514             assertFalse(Thread.interrupted());
 515         }
 516     }
 517 
 518     public void testAwaitTermination12() throws Exception {
 519         var fiber = FiberScope.background().schedule(() -> LockSupport.park());
 520         Thread.currentThread().interrupt();
 521         try {
 522             fiber.awaitTermination(Duration.ofSeconds(0));
 523             assertTrue(false);
 524         } catch (InterruptedException e) {
 525             LockSupport.unpark(fiber);
 526             assertFalse(Thread.interrupted());
 527         }
 528     }
 529 
 530     public void testAwaitTermination13() throws Exception {
 531         var fiber = FiberScope.background().schedule(() -> "foo");
 532         fiber.join();
 533 
 534         FiberScope.background().schedule(() -> {
 535             Thread.currentThread().interrupt();
 536             try {
 537                 fiber.awaitTermination(Duration.ofSeconds(0));
 538                 assertTrue(false);
 539             } catch (InterruptedException e) {
 540                 assertFalse(Thread.interrupted());
 541             }
 542         }).join();
 543     }
 544 
 545     public void testAwaitTermination14() throws Exception {
 546         var fiber = FiberScope.background().schedule(() -> LockSupport.park());
 547         try {
 548             FiberScope.background().schedule(() -> {
 549                 Thread.currentThread().interrupt();
 550                 try {
 551                     fiber.awaitTermination(Duration.ofSeconds(0));
 552                     assertTrue(false);
 553                 } catch (InterruptedException e) {
 554                     assertFalse(Thread.interrupted());
 555                 }
 556             }).join();
 557         } finally {
 558             LockSupport.unpark(fiber);
 559         }
 560     }
 561 
 562     @Test(expectedExceptions = { NullPointerException.class })
 563     public void testAwaitTermination15() throws Exception {
 564         var fiber = FiberScope.background().schedule(() -> "foo");
 565         fiber.awaitTermination(null);
 566     }
 567 
 568 
 569     // -- Fiber.current --
 570 
 571     public void testCurrent1() throws Exception {
 572         assertTrue(Fiber.current().isEmpty());
 573     }
 574 
 575     public void testCurrent2() throws Exception {
 576         var fiber = FiberScope.background().schedule(() -> Fiber.current());
 577         var current = fiber.join().orElseThrow();
 578         assertTrue(current == fiber);
 579     }
 580 
 581 
 582     // -- cancellation --
 583 
 584     // sets cancel status
 585     public void testCancel1() throws Exception {
 586         FiberScope.background().schedule(() -> {
 587             var fiber = Fiber.current().orElseThrow();
 588             assertFalse(fiber.isCancelled());
 589             assertTrue(fiber.cancel());
 590             assertTrue(fiber.isCancelled());
 591             assertFalse(fiber.cancel());    // already set
 592             assertTrue(fiber.isCancelled());
 593         }).join();
 594     }
 595 
 596     // unparks and interrupts
 597     public void testCancel2() throws Exception {
 598         var fiber = FiberScope.background().schedule(() -> {
 599             LockSupport.park();
 600             assertTrue(Thread.currentThread().isInterrupted());
 601         });
 602         Thread.sleep(50);
 603         assertTrue(fiber.cancel());
 604         fiber.join();
 605     }
 606 
 607     // cancel Future
 608     public void testCancel3() throws Exception {
 609         var fiber = FiberScope.background().schedule(() -> {
 610             LockSupport.park();
 611         });
 612         var future = fiber.toFuture();
 613         fiber.cancel();
 614         assertTrue(future.isCancelled());
 615         try {
 616             future.join();
 617             assertTrue(false);
 618         } catch (CancellationException e) {
 619             // expected
 620         }
 621     }
 622 
 623 
 624     // -- isAlive --
 625 
 626     public void testIsAlive() throws Exception {
 627         var fiber = FiberScope.background().schedule(() -> LockSupport.park());
 628         assertTrue(fiber.isAlive());
 629         LockSupport.unpark(fiber);
 630         fiber.join();
 631         assertFalse(fiber.isAlive());
 632     }
 633 
 634 
 635     // -- toFuture --
 636 
 637     public void testToFuture1() throws Exception {
 638         Future<String> result = FiberScope.background().schedule(() -> "foo").toFuture();
 639         String s = result.get();
 640         assertEquals(s, "foo");
 641     }
 642 
 643     public void testToFuture2() throws Exception {
 644         Future<?> future = FiberScope.background().schedule(() -> {
 645             throw new RuntimeException();
 646         }).toFuture();
 647         try {
 648             future.get();
 649             assertTrue(false);
 650         } catch (ExecutionException e) {
 651             assertTrue(e.getCause() instanceof RuntimeException);
 652         }
 653     }
 654 
 655     // Future.cancel
 656     public void testFuture3() throws Exception {
 657         Fiber<Boolean> fiber = FiberScope.background().schedule(() -> {
 658             LockSupport.park();
 659             return Thread.currentThread().isInterrupted();
 660         });
 661         Future<?> result = fiber.toFuture();
 662 
 663         // sets cancel status and unpark fiber
 664         boolean x = result.cancel(true);
 665         System.out.println("x=" + x);
 666 
 667         try {
 668             result.get();
 669             assertTrue(false);
 670         } catch (CancellationException expected) { }
 671 
 672         // fiber returns interrupt status
 673         assertTrue(fiber.join() == true);
 674     }
 675 
 676     // Fiber.toFuture should return the same object is called several times
 677     public void testToFuture4() {
 678         var fiber = FiberScope.background().schedule(() -> "foo");
 679         var result1 = fiber.toFuture();
 680         var result2 = fiber.toFuture();
 681         assertTrue(result1 == result2);
 682     }
 683 
 684 
 685     // -- Thread.currentThread --
 686 
 687     //  Thread.currentThread before/after park
 688     public void testCurrentThread1() throws Exception {
 689         var fiber = FiberScope.background().schedule(() -> {
 690             Thread t = Thread.currentThread();  // before park
 691             LockSupport.park();
 692             assertTrue(Thread.currentThread() == t);  // after park
 693             return null;
 694         });
 695         Thread.sleep(1000); // give time for fiber to park
 696         LockSupport.unpark(fiber);
 697         fiber.join();
 698     }
 699 
 700     //  Thread.currentThread before/after synchronized block
 701     public void testCurrentThread2() throws Exception {
 702         var lock = new Object();
 703         Fiber<?> fiber;
 704         synchronized (lock) {
 705             fiber = FiberScope.background().schedule(() -> {
 706                 Thread t = Thread.currentThread();  // before synchronized
 707                 synchronized (lock) { }
 708                 assertTrue(Thread.currentThread() == t);  // after synchronized
 709                 return null;
 710             });
 711             Thread.sleep(200); // give time for fiber to block
 712         }
 713         fiber.join();
 714     }
 715 
 716     //  Thread.currentThread before/after lock
 717     public void testCurrentThread3() throws Exception {
 718         var lock = new ReentrantLock();
 719         Fiber<?> fiber;
 720         lock.lock();
 721         try {
 722             fiber = FiberScope.background().schedule(() -> {
 723                 Thread t = Thread.currentThread();  // before lock
 724                 lock.lock();
 725                 lock.unlock();
 726                 assertTrue(Thread.currentThread() == t);  // after lock
 727                 return null;
 728             });
 729             Thread.sleep(200); // give time for fiber to block
 730         } finally {
 731             lock.unlock();
 732         }
 733         fiber.join();
 734     }
 735 
 736 
 737     // -- Thread.start/stop/suspend/resume --
 738 
 739     public void testThreadStart() throws Exception {
 740         var fiber = FiberScope.background().schedule(() -> {
 741             Thread t = Thread.currentThread();
 742             try {
 743                 t.start();
 744                 assertTrue(false);
 745             } catch (IllegalStateException e) {
 746                 // expected
 747             }
 748         });
 749         fiber.join();
 750     }
 751 
 752     public void testThreadStop() throws Exception {
 753         var fiber = FiberScope.background().schedule(() -> {
 754             Thread t = Thread.currentThread();
 755             try {
 756                 t.stop();
 757                 assertTrue(false);
 758             } catch (UnsupportedOperationException e) {
 759                 // expected
 760             }
 761         });
 762         fiber.join();
 763     }
 764 
 765     public void testThreadSuspend() throws Exception {
 766         var fiber = FiberScope.background().schedule(() -> {
 767             Thread t = Thread.currentThread();
 768             try {
 769                 t.suspend();
 770                 assertTrue(false);
 771             } catch (UnsupportedOperationException e) {
 772                 // expected
 773             }
 774         });
 775         fiber.join();
 776     }
 777 
 778     public void testThreadResume() throws Exception {
 779         var fiber = FiberScope.background().schedule(() -> {
 780             Thread t = Thread.currentThread();
 781             try {
 782                 t.resume();
 783                 assertTrue(false);
 784             } catch (UnsupportedOperationException e) {
 785                 // expected
 786             }
 787         });
 788         fiber.join();
 789     }
 790 
 791 
 792     // -- Thread.join --
 793 
 794     // thread invokes join to wait for fiber to terminate
 795     public void testThreadJoin1() throws Exception {
 796         Thread t = FiberScope.background().schedule(() -> Thread.currentThread()).join();
 797         t.join();
 798     }
 799 
 800     // fiber invokes join to wait for another fiber to terminate
 801     public void testThreadJoin2() throws Exception {
 802         Thread t = FiberScope.background().schedule(() -> Thread.currentThread()).join();
 803         var fiber2 = FiberScope.background().schedule(() -> {
 804             t.join();
 805             return null;
 806         });
 807         fiber2.join();
 808     }
 809 
 810     // thread invokes join(millis) to wait for fiber to terminate
 811     public void testThreadJoin3() throws Exception {
 812         Thread t = FiberScope.background().schedule(() -> Thread.currentThread()).join();
 813         t.join(10*1000);
 814     }
 815 
 816     // fiber invokes join(millis) to wait for another fiber to terminate
 817     public void testThreadJoin4() throws Exception {
 818         Thread t = FiberScope.background().schedule(() -> Thread.currentThread()).join();
 819         var fiber2 = FiberScope.background().schedule(() -> {
 820             t.join(10*1000);
 821             return null;
 822         });
 823         fiber2.join();
 824     }
 825 
 826     // thread invokes join(millis), fiber does not terminate
 827     public void testThreadJoin5() throws Exception {
 828         var ref = new AtomicReference<Thread>();
 829         var fiber = FiberScope.background().schedule(() -> {
 830             ref.set(Thread.currentThread());
 831             LockSupport.park();
 832         });
 833         Thread t = waitForValue(ref);
 834         try {
 835             t.join(2*1000);
 836         } finally {
 837             LockSupport.unpark(fiber);
 838         }
 839     }
 840 
 841     // fiber invokes join(millis) to wait for other fiber that does not terminate
 842     public void testThreadJoin6() throws Exception {
 843         var ref = new AtomicReference<Thread>();
 844         var fiber1 = FiberScope.background().schedule(() -> {
 845             ref.set(Thread.currentThread());
 846             LockSupport.park();
 847         });
 848         var fiber2 = FiberScope.background().schedule(() -> {
 849             Thread t = waitForValue(ref);
 850             t.join(2*1000);
 851             return null;
 852         });
 853         try {
 854             fiber2.join();
 855         } finally {
 856             LockSupport.unpark(fiber1);
 857         }
 858     }
 859 
 860     // interrupt before Thread.join main thread
 861     public void testThreadJoin7() throws Exception {
 862         Thread mainThread = Thread.currentThread();
 863         var fiber = FiberScope.background().schedule(() -> {
 864             Thread t = Thread.currentThread();
 865             t.interrupt();
 866             try {
 867                 mainThread.join();
 868                 assertTrue(false);
 869             } catch (InterruptedException e) {
 870                 // interrupt status should be cleared
 871                 assertFalse(t.isInterrupted());
 872             }
 873             return null;
 874         });
 875         fiber.join();
 876     }
 877 
 878     // interrupt before Thread.join current thread
 879     public void testThreadJoin8() throws Exception {
 880         var fiber = FiberScope.background().schedule(() -> {
 881             Thread t = Thread.currentThread();
 882             t.interrupt();
 883             try {
 884                 t.join();
 885                 assertTrue(false);
 886             } catch (InterruptedException e) {
 887                 // interrupt status should be cleared
 888                 assertFalse(t.isInterrupted());
 889             }
 890             return null;
 891         });
 892         fiber.join();
 893     }
 894 
 895     // interrupt while in Thread.join
 896     public void testThreadJoin9() throws Exception {
 897         Thread mainThread = Thread.currentThread();
 898         var fiber = FiberScope.background().schedule(() -> {
 899             Thread t = Thread.currentThread();
 900             Interrupter.schedule(t, 1000);
 901             try {
 902                 mainThread.join();
 903                 assertTrue(false);
 904             } catch (InterruptedException e) {
 905                 // interrupt status should be cleared
 906                 assertFalse(t.isInterrupted());
 907             }
 908             return null;
 909         });
 910         fiber.join();
 911     }
 912 
 913     // interrupt while in Thread.join current thread
 914     public void testThreadJoin10() throws Exception {
 915         var fiber = FiberScope.background().schedule(() -> {
 916             Thread t = Thread.currentThread();
 917             try {
 918                 Interrupter.schedule(t, 1000);
 919                 t.join();
 920                 assertTrue(false);
 921             } catch (InterruptedException e) {
 922                 // interrupt status should be cleared
 923                 assertFalse(Thread.currentThread().isInterrupted());
 924             }
 925             return null;
 926         });
 927         fiber.join();
 928     }
 929 
 930     // join with negative timeout
 931     @Test(expectedExceptions = { IllegalArgumentException.class })
 932     public void testThreadJoin11() throws Exception {
 933         Thread t = FiberScope.background().schedule(() -> Thread.currentThread()).join();
 934         t.join(-1);
 935     }
 936 
 937 
 938     // -- Thread.yield --
 939 
 940     public void testThreadYield1() throws Exception {
 941         var list = new CopyOnWriteArrayList<String>();
 942         ExecutorService scheduler = Executors.newFixedThreadPool(1);
 943         try {
 944             FiberScope.background().schedule(scheduler, () -> {
 945                 list.add("A");
 946                 Fiber<?> child = FiberScope.background().schedule(scheduler, () -> {
 947                     list.add("B");
 948                     Thread.yield();
 949                     list.add("B");
 950                 });
 951                 Thread.yield();
 952                 list.add("A");
 953                 child.join();
 954                 return null;
 955             }).join();
 956         } finally {
 957             scheduler.shutdown();
 958         }
 959         assertEquals(list, List.of("A", "B", "A", "B"));
 960     }
 961 
 962     public void testThreadYield2() throws Exception {
 963         var list = new CopyOnWriteArrayList<String>();
 964         ExecutorService scheduler = Executors.newFixedThreadPool(1);
 965         try {
 966             FiberScope.background().schedule(scheduler, () -> {
 967                 list.add("A");
 968                 Fiber<?> child = FiberScope.background().schedule(scheduler, () -> {
 969                     list.add("B");
 970                 });
 971                 Object lock = new Object();
 972                 synchronized (lock) {
 973                     Thread.yield();   // pinned so will be a no-op
 974                     list.add("A");
 975                 }
 976                 child.join();
 977                 return null;
 978             }).join();
 979         } finally {
 980             scheduler.shutdown();
 981         }
 982         assertEquals(list, List.of("A", "A", "B"));
 983     }
 984 
 985 
 986     // -- Thread.sleep --
 987 
 988     // Thread.sleep(-1)
 989     public void testThreadSleep1() throws Exception {
 990         var fiber = FiberScope.background().schedule(() -> {
 991             try {
 992                 Thread.sleep(-1);
 993                 assertTrue(false);
 994             } catch (IllegalArgumentException e) {
 995                 // expected
 996             }
 997             return null;
 998         });
 999         fiber.join();
1000     }
1001 
1002     // Thread.sleep(0)
1003     public void testThreadSleep2() throws Exception {
1004         var fiber = FiberScope.background().schedule(() -> {
1005             Thread.sleep(0);
1006             return null;
1007         });
1008         fiber.join();
1009     }
1010 
1011     // Thread.sleep(2000)
1012     public void testThreadSleep3() throws Exception {
1013         var fiber = FiberScope.background().schedule(() -> {
1014             long start = System.currentTimeMillis();
1015             Thread.sleep(2000);
1016             long elapsed = System.currentTimeMillis() - start;
1017             assertTrue(elapsed > 1900);
1018             return null;
1019         });
1020         fiber.join();
1021     }
1022 
1023     // Thread.sleep with interrupt status set
1024     public void testThreadSleep4() throws Exception {
1025         var fiber = FiberScope.background().schedule(() -> {
1026             Thread.currentThread().interrupt();
1027             try {
1028                 Thread.sleep(1000);
1029                 assertTrue(false);
1030             } catch (InterruptedException e) {
1031                 // expected
1032             }
1033             return null;
1034         });
1035         fiber.join();
1036     }
1037 
1038     // Thread.sleep interrupted while sleeping
1039     public void testThreadSleep5() throws Exception {
1040         var completed = new AtomicBoolean();
1041         var fiber = FiberScope.background().schedule(() -> {
1042             Thread t = Thread.currentThread();
1043             Interrupter.schedule(t, 2000);
1044             try {
1045                 Thread.sleep(20*1000);
1046                 assertTrue(false);
1047             } catch (InterruptedException e) {
1048                 // interrupt status should be clearer
1049                 assertFalse(t.isInterrupted());
1050             }
1051             return null;
1052         });
1053         fiber.join();
1054     }
1055 
1056     // Thread.sleep should not be disrupted by unparking fiber
1057     public void testThreadSleep6() throws Exception {
1058         var fiber = FiberScope.background().schedule(() -> {
1059             long start = System.currentTimeMillis();
1060             Thread.sleep(2000);
1061             long elapsed = System.currentTimeMillis() - start;
1062             assertTrue(elapsed > 1900);
1063             return null;
1064         });
1065         // attempt to disrupt sleep
1066         for (int i=0; i<5; i++) {
1067             Thread.sleep(20);
1068             LockSupport.unpark(fiber);
1069         }
1070         fiber.join();
1071     }
1072 
1073     // -- ThreadLocal --
1074 
1075     static final ThreadLocal<Object> LOCAL = new ThreadLocal<>();
1076     static final ThreadLocal<Object> INHERITED_LOCAL = new InheritableThreadLocal<>();
1077 
1078     public void testThreadLocal1() throws Exception {
1079         for (int i = 0; i < 10; i++) {
1080             FiberScope.background().schedule(() -> {
1081                 assertTrue(LOCAL.get() == null);
1082                 Object obj = new Object();
1083                 LOCAL.set(obj);
1084                 assertTrue(LOCAL.get() == obj);
1085             }).join();
1086         }
1087     }
1088 
1089     public void testThreadLocal2() throws Exception {
1090         var fiber = FiberScope.background().schedule(() -> {
1091             assertTrue(LOCAL.get() == null);
1092             Object obj = new Object();
1093             LOCAL.set(obj);
1094             try { Thread.sleep(100); } catch (InterruptedException e) { }
1095             assertTrue(LOCAL.get() == obj);
1096         });
1097         fiber.join();
1098     }
1099 
1100     public void testInheritedThreadLocal1() throws Exception {
1101         assertTrue(INHERITED_LOCAL.get() == null);
1102 
1103         for (int i = 0; i < 10; i++) {
1104             var fiber = FiberScope.background().schedule(() -> {
1105                 assertTrue(INHERITED_LOCAL.get() == null);
1106                 Object obj = new Object();
1107                 INHERITED_LOCAL.set(obj);
1108                 assertTrue(INHERITED_LOCAL.get() == obj);
1109             });
1110             fiber.join();
1111         }
1112 
1113         assertTrue(INHERITED_LOCAL.get() == null);
1114     }
1115 
1116     // inherit thread local from creating thread
1117     public void testInheritedThreadLocal2() throws Exception {
1118         assertTrue(INHERITED_LOCAL.get() == null);
1119 
1120         var obj = new Object();
1121         INHERITED_LOCAL.set(obj);
1122         try {
1123             var fiber = FiberScope.background().schedule(INHERITED_LOCAL::get);
1124             assert fiber.join() == obj;
1125         } finally {
1126             INHERITED_LOCAL.remove();
1127         }
1128     }
1129 
1130     // inherit thread local from creating fiber
1131     public void testInheritedThreadLocal3() throws Exception {
1132         assertTrue(INHERITED_LOCAL.get() == null);
1133         var fiber = FiberScope.background().schedule(() -> {
1134             var obj = new Object();
1135             INHERITED_LOCAL.set(obj);
1136             var inherited = FiberScope.background().schedule(INHERITED_LOCAL::get).join();
1137             assertTrue(inherited == obj);
1138             return null;
1139         });
1140         fiber.join();
1141     }
1142 
1143 
1144     // inherit context class loader from creating fiber
1145     public void testInheritedThreadLocal4() throws Exception {
1146         assertTrue(INHERITED_LOCAL.get() == null);
1147         var obj = new Object();
1148         INHERITED_LOCAL.set(obj);
1149         try {
1150             var fiber = FiberScope.background().schedule(() ->
1151                     FiberScope.background().schedule(INHERITED_LOCAL::get).join());
1152             var inherited = fiber.join();
1153             assertTrue(inherited == obj);
1154         } finally {
1155             INHERITED_LOCAL.remove();
1156         }
1157     }
1158 
1159 
1160     // -- Thread.set/getContextClassLoader --
1161 
1162     public void testThreadContextClassLoader1() throws Exception {
1163         ClassLoader loader = new ClassLoader() { };
1164         FiberScope.background().schedule(() -> {
1165             Thread t = Thread.currentThread();
1166             t.setContextClassLoader(loader);
1167             assertTrue(t.getContextClassLoader() == loader);
1168         }).join();
1169         assertTrue(Thread.currentThread().getContextClassLoader() != loader);
1170     }
1171 
1172     // inherit context class loader from creating thread
1173     public void testThreadContextClassLoader2() throws Exception {
1174         ClassLoader loader = new ClassLoader() { };
1175         Thread t = Thread.currentThread();
1176         ClassLoader savedLoader = t.getContextClassLoader();
1177         t.setContextClassLoader(loader);
1178         try {
1179             FiberScope.background().schedule(() -> {
1180                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1181             }).join();
1182         } finally {
1183             t.setContextClassLoader(savedLoader);
1184         }
1185     }
1186 
1187     // inherit context class loader from creating fiber
1188     public void testThreadContextClassLoader3() throws Exception {
1189         ClassLoader loader = new ClassLoader() { };
1190         FiberScope.background().schedule(() -> {
1191             Thread.currentThread().setContextClassLoader(loader);
1192             FiberScope.background().schedule(() -> {
1193                 assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1194             }).join();
1195             return null;
1196         }).join();
1197     }
1198 
1199     // inherit context class loader from creating fiber
1200     public void testThreadContextClassLoader4() throws Exception {
1201         ClassLoader loader = new ClassLoader() { };
1202         Thread t = Thread.currentThread();
1203         ClassLoader savedLoader = t.getContextClassLoader();
1204         t.setContextClassLoader(loader);
1205         try {
1206             FiberScope.background().schedule(() -> {
1207                 FiberScope.background().schedule(() -> {
1208                     assertTrue(Thread.currentThread().getContextClassLoader() == loader);
1209                 }).join();
1210                 return null;
1211             }).join();
1212         } finally {
1213             t.setContextClassLoader(savedLoader);
1214         }
1215     }
1216 
1217 
1218     // -- Thread.setUncaughtExceptionHandler --
1219 
1220     public void testThreadUncaughtExceptionHandler1() throws Exception {
1221         class FooException extends RuntimeException { }
1222         var exception = new AtomicReference<Throwable>();
1223         Thread.UncaughtExceptionHandler handler = (thread, exc) -> {
1224             exception.set(exc);
1225         };
1226         var fiber = FiberScope.background().schedule(() -> {
1227             Thread.currentThread().setUncaughtExceptionHandler(handler);
1228             throw new FooException();
1229         });
1230         while (fiber.isAlive()) {
1231             Thread.sleep(10);
1232         }
1233         assertTrue(exception.get() instanceof FooException);
1234     }
1235 
1236 
1237     // -- Thread.getId --
1238 
1239     public void testThreadGetId() throws Exception {
1240         try (var scope = FiberScope.open()) {
1241             long id1 = scope.schedule(() -> Thread.currentThread().getId()).join();
1242             long id2 = scope.schedule(() -> Thread.currentThread().getId()).join();
1243             long id3 = Thread.currentThread().getId();
1244             assertTrue(id1 != id2);
1245             assertTrue(id1 != id3);
1246             assertTrue(id2 != id3);
1247         }
1248     }
1249 
1250 
1251     // -- Thread.getState --
1252 
1253     // RUNNABLE
1254     public void testThreadGetState1() throws Exception {
1255         var fiber = FiberScope.background().schedule(() -> {
1256             Thread.State state = Thread.currentThread().getState();
1257             assertTrue(state == Thread.State.RUNNABLE);
1258         });
1259         fiber.join();
1260     }
1261 
1262     // WAITING when parked
1263     public void testThreadGetState2() throws Exception {
1264         var ref = new AtomicReference<Thread>();
1265         var fiber = FiberScope.background().schedule(() -> {
1266             ref.set(Thread.currentThread());
1267             LockSupport.park();
1268         });
1269         Thread t = waitForValue(ref);
1270         while (t.getState() != Thread.State.WAITING) {
1271             Thread.sleep(20);
1272         }
1273         LockSupport.unpark(fiber);
1274         fiber.join();
1275     }
1276 
1277     // WAITING when parked and pinned
1278     public void testThreadGetState3() throws Exception {
1279         var ref = new AtomicReference<Thread>();
1280         var fiber = FiberScope.background().schedule(() -> {
1281             ref.set(Thread.currentThread());
1282             var lock = new Object();
1283             synchronized (lock) {
1284                 LockSupport.park();
1285             }
1286         });
1287         Thread t = waitForValue(ref);
1288         while (t.getState() != Thread.State.WAITING) {
1289             Thread.sleep(20);
1290         }
1291         LockSupport.unpark(fiber);
1292         fiber.join();
1293     }
1294 
1295     // WAITING when blocked in Object.wait
1296     public void testThreadGetState4() throws Exception {
1297         var ref = new AtomicReference<Thread>();
1298         var lock = new Object();
1299         var fiber = FiberScope.background().schedule(() -> {
1300             ref.set(Thread.currentThread());
1301             synchronized (lock) {
1302                 try { lock.wait(); } catch (InterruptedException e) { }
1303             }
1304         });
1305         Thread t = waitForValue(ref);
1306         while (t.getState() != Thread.State.WAITING) {
1307             Thread.sleep(20);
1308         }
1309         t.interrupt();
1310         fiber.join();
1311     }
1312 
1313     // TERMINATED
1314     public void testThreadGetState5() throws Exception {
1315         var fiber = FiberScope.background().schedule(() -> Thread.currentThread());
1316         Thread t = fiber.join();
1317         assertTrue(t.getState() == Thread.State.TERMINATED);
1318     }
1319 
1320 
1321     // -- Thread.holdsLock --
1322 
1323     public void testThreadHoldsLock1() throws Exception {
1324         var fiber = FiberScope.background().schedule(() -> {
1325             var lock = new Object();
1326             assertFalse(Thread.holdsLock(lock));
1327         });
1328         fiber.join();
1329     }
1330 
1331     public void testThreadHoldsLock2() throws Exception {
1332         var fiber = FiberScope.background().schedule(() -> {
1333             var lock = new Object();
1334             synchronized (lock) {
1335                 assertTrue(Thread.holdsLock(lock));
1336             }
1337         });
1338         fiber.join();
1339     }
1340 
1341 
1342     // -- Thread.getStackTrace --
1343 
1344     // runnable (mounted)
1345     public void testThreadGetStackTrace1() throws Exception {
1346         var ref = new AtomicReference<Thread>();
1347         var sel = Selector.open();
1348         FiberScope.background().schedule(() -> doSelect(ref, sel));
1349         Thread thread = waitForValue(ref);
1350         try {
1351             assertTrue(thread.getState() == Thread.State.RUNNABLE);
1352             StackTraceElement[] stack = thread.getStackTrace();
1353             assertTrue(contains(stack, "doSelect"));
1354         } finally {
1355             sel.close();
1356         }
1357     }
1358 
1359     // block in Selector.select after recording current thread
1360     private void doSelect(AtomicReference<Thread> ref, Selector sel) {
1361         ref.set(Thread.currentThread());
1362         try { sel.select(); } catch (Exception e) { }
1363     }
1364 
1365 
1366     // waiting (mounted)
1367     public void testThreadGetStackTrace2() throws Exception {
1368         var lock = new Object();
1369         var ref = new AtomicReference<Thread>();
1370         FiberScope.background().schedule(() -> {
1371             synchronized (lock) {
1372                 ref.set(Thread.currentThread());
1373                 try { lock.wait(); } catch (InterruptedException e) { }
1374             }
1375         });
1376 
1377         // wait for carrier thread to block
1378         Thread thread = waitForValue(ref);
1379         while (thread.getState() != Thread.State.WAITING) {
1380             Thread.sleep(20);
1381         }
1382 
1383         try {
1384             StackTraceElement[] stack = thread.getStackTrace();
1385             assertTrue(contains(stack, "Object.wait"));
1386         } finally {
1387             synchronized (lock) {
1388                 lock.notifyAll();
1389             }
1390         }
1391     }
1392 
1393     // parked (unmounted)
1394     public void testThreadGetStackTrace3() throws Exception {
1395         var ref = new AtomicReference<Thread>();
1396         var fiber = FiberScope.background().schedule(() -> {
1397             ref.set(Thread.currentThread());
1398             LockSupport.park();
1399         });
1400 
1401         // wait for fiber to park
1402         Thread thread = waitForValue(ref);
1403         while (thread.getState() != Thread.State.WAITING) {
1404             Thread.sleep(20);
1405         }
1406 
1407         try {
1408             StackTraceElement[] stack = thread.getStackTrace();
1409             assertTrue(contains(stack, "LockSupport.park"));
1410         } finally {
1411             LockSupport.unpark(fiber);
1412         }
1413     }
1414 
1415     // terminated
1416     public void testThreadGetStackTrace4() throws Exception {
1417         var fiber = FiberScope.background().schedule(() -> Thread.currentThread());
1418         var thread = fiber.join();
1419         StackTraceElement[] stack = thread.getStackTrace();
1420         assertTrue(stack.length == 0);
1421     }
1422 
1423     private boolean contains(StackTraceElement[] stack, String expected) {
1424         return Stream.of(stack)
1425                 .map(Object::toString)
1426                 .anyMatch(s -> s.contains(expected));
1427     }
1428 
1429     // -- ThreadGroup --
1430 
1431     // ThreadGroup.enumerate should not enumerate fiber Thread objects
1432     public void testThreadGroup1() throws Exception {
1433         var fiber = FiberScope.background().schedule(() -> {
1434             Thread current = Thread.currentThread();
1435 
1436             ThreadGroup g = Thread.currentThread().getThreadGroup();
1437             Thread[] threads = new Thread[100];
1438             g.enumerate(threads);
1439             Stream.of(threads)
1440                     .filter(t -> t == current)
1441                     .forEach(t -> assertTrue(false));
1442         });
1443         fiber.join();
1444     }
1445 
1446     // ThreadGroup.interrupt should not interrupt fiber Thread objects
1447     public void testThreadGroup2() throws Exception {
1448         var fiber = FiberScope.background().schedule(() -> {
1449             Thread t = Thread.currentThread();
1450             t.getThreadGroup().interrupt();
1451             assertFalse(t.isInterrupted());
1452         });
1453         fiber.join();
1454     }
1455 
1456 
1457     // -- Object.wait/notify --
1458 
1459     // fiber waits, notified by thread
1460     public void testWaitNotify1() throws Exception {
1461         var lock = new Object();
1462         var ready = new Semaphore(0);
1463         var fiber = FiberScope.background().schedule(() -> {
1464             synchronized (lock) {
1465                 ready.release();
1466                 try {
1467                     lock.wait();
1468                 } catch (InterruptedException e) { }
1469             }
1470         });
1471         // thread invokes notify
1472         ready.acquire();
1473         synchronized (lock) {
1474             lock.notifyAll();
1475         }
1476         fiber.join();
1477     }
1478 
1479     // thread waits, notified by fiber
1480     public void testWaitNotify2() throws Exception {
1481         var lock = new Object();
1482         var ready = new Semaphore(0);
1483         FiberScope.background().schedule(() -> {
1484             ready.acquire();
1485             synchronized (lock) {
1486                 lock.notifyAll();
1487             }
1488             return null;
1489         });
1490         synchronized (lock) {
1491             ready.release();
1492             lock.wait();
1493         }
1494     }
1495 
1496     // fiber waits, notified by other fiber
1497     public void testWaitNotify3() throws Exception {
1498         var lock = new Object();
1499         var ready = new Semaphore(0);
1500         var fiber1 = FiberScope.background().schedule(() -> {
1501             synchronized (lock) {
1502                 ready.release();
1503                 try {
1504                     lock.wait();
1505                 } catch (InterruptedException e) { }
1506             }
1507         });
1508         var fiber2 = FiberScope.background().schedule(() -> {
1509             ready.acquire();
1510             synchronized (lock) {
1511                 lock.notifyAll();
1512             }
1513             return null;
1514         });
1515         fiber1.join();
1516         fiber2.join();
1517     }
1518 
1519     // interrupt before Object.wait
1520     public void testWaitNotify4() throws Exception {
1521         var fiber = FiberScope.background().schedule(() -> {
1522             Thread t = Thread.currentThread();
1523             t.interrupt();
1524             Object lock = new Object();
1525             synchronized (lock) {
1526                 try {
1527                     lock.wait();
1528                     assertTrue(false);
1529                 } catch (InterruptedException e) {
1530                     // interrupt status should be cleared
1531                     assertFalse(t.isInterrupted());
1532                 }
1533             }
1534         });
1535         fiber.join();
1536     }
1537 
1538     // interrupt while waiting in Object.wait
1539     public void testWaitNotify5() throws Exception {
1540         var fiber = FiberScope.background().schedule(() -> {
1541             Thread t = Thread.currentThread();
1542             Interrupter.schedule(t, 1000);
1543             Object lock = new Object();
1544             synchronized (lock) {
1545                 try {
1546                     lock.wait();
1547                     assertTrue(false);
1548                 } catch (InterruptedException e) {
1549                     // interrupt status should be cleared
1550                     assertFalse(t.isInterrupted());
1551                 }
1552             }
1553         });
1554         fiber.join();
1555     }
1556 
1557 
1558     // -- ReentrantLock --
1559 
1560     // lock/unlock
1561     public void testReentrantLock1() throws Exception {
1562         var fiber = FiberScope.background().schedule(() -> {
1563             ReentrantLock lock = new ReentrantLock();
1564             assertFalse(lock.isHeldByCurrentThread());
1565             lock.lock();
1566             assertTrue(lock.isHeldByCurrentThread());
1567             lock.unlock();
1568             assertFalse(lock.isHeldByCurrentThread());
1569         });
1570         fiber.join();
1571     }
1572 
1573     // tryLock/unlock
1574     public void testReentrantLock2() throws Exception {
1575         var fiber = FiberScope.background().schedule(() -> {
1576             ReentrantLock lock = new ReentrantLock();
1577             assertFalse(lock.isHeldByCurrentThread());
1578             boolean acquired = lock.tryLock();
1579             assertTrue(acquired);
1580             assertTrue(lock.isHeldByCurrentThread());
1581             lock.unlock();
1582             assertFalse(lock.isHeldByCurrentThread());
1583         });
1584         fiber.join();
1585     }
1586 
1587     // lock/lock/unlock/unlock
1588     public void testReentrantLock3() throws Exception {
1589         var fiber = FiberScope.background().schedule(() -> {
1590             ReentrantLock lock = new ReentrantLock();
1591             assertFalse(lock.isHeldByCurrentThread());
1592             assertTrue(lock.getHoldCount() == 0);
1593             lock.lock();
1594             assertTrue(lock.isHeldByCurrentThread());
1595             assertTrue(lock.getHoldCount() == 1);
1596             lock.lock();
1597             assertTrue(lock.isHeldByCurrentThread());
1598             assertTrue(lock.getHoldCount() == 2);
1599             lock.unlock();
1600             assertTrue(lock.isHeldByCurrentThread());
1601             assertTrue(lock.getHoldCount() == 1);
1602             lock.unlock();
1603             assertFalse(lock.isHeldByCurrentThread());
1604             assertTrue(lock.getHoldCount() == 0);
1605         });
1606         fiber.join();
1607     }
1608 
1609     // locked by thread, fiber tries to lock
1610     public void testReentrantLock4() throws Exception {
1611         Fiber<?> fiber;
1612         ReentrantLock lock = new ReentrantLock();
1613         var holdsLock = new AtomicBoolean();
1614 
1615         // thread acquires lock
1616         lock.lock();
1617         try {
1618             fiber = FiberScope.background().schedule(() -> {
1619                 lock.lock();  // should block
1620                 holdsLock.set(true);
1621                 LockSupport.park();
1622                 lock.unlock();
1623                 holdsLock.set(false);
1624             });
1625             // give time for fiber to block
1626             Thread.sleep(1000);
1627             assertFalse(holdsLock.get());
1628         } finally {
1629             lock.unlock();
1630         }
1631 
1632         // fiber should acquire lock, park, unpark, and then release lock
1633         while (!holdsLock.get()) {
1634             Thread.sleep(20);
1635         }
1636         LockSupport.unpark(fiber);
1637         while (holdsLock.get()) {
1638             Thread.sleep(20);
1639         }
1640     }
1641 
1642     // locked by fiber, thread tries to lock
1643     public void testReentrantLock5() throws Exception {
1644         ReentrantLock lock = new ReentrantLock();
1645         var fiber = FiberScope.background().schedule(() -> {
1646             lock.lock();
1647             try {
1648                 LockSupport.park();
1649             } finally {
1650                 lock.unlock();
1651             }
1652         });
1653 
1654         // wat for fiber to acquire lock
1655         while (!lock.isLocked()) {
1656             Thread.sleep(20);
1657         }
1658 
1659         // thread cannot acquire lock
1660         try {
1661             assertFalse(lock.tryLock());
1662         } finally {
1663             // fiber should unlock
1664             LockSupport.unpark(fiber);
1665 
1666             // thread should be able to acquire lock
1667             lock.lock();
1668             lock.unlock();
1669 
1670             fiber.join();
1671         }
1672     }
1673 
1674     // lock by fiber, another fiber tries to lock
1675     public void testReentrantLock6() throws Exception {
1676         ReentrantLock lock = new ReentrantLock();
1677         var fiber1 = FiberScope.background().schedule(() -> {
1678             lock.lock();
1679             try {
1680                 LockSupport.park();
1681             } finally {
1682                 lock.unlock();
1683             }
1684         });
1685 
1686         // wat for fiber to acquire lock
1687         while (!lock.isLocked()) {
1688             Thread.sleep(10);
1689         }
1690 
1691         var holdsLock  = new AtomicBoolean();
1692         var fiber2 = FiberScope.background().schedule(() -> {
1693             lock.lock();
1694             holdsLock.set(true);
1695             LockSupport.park();
1696             lock.unlock();
1697             holdsLock.set(false);
1698         });
1699 
1700         // fiber2 should block
1701         Thread.sleep(1000);
1702         assertFalse(holdsLock.get());
1703 
1704         // unpark fiber1
1705         LockSupport.unpark(fiber1);
1706 
1707         // fiber2 should acquire lock
1708         while (!holdsLock.get()) {
1709             Thread.sleep(20);
1710         }
1711         // unpark fiber and it should release lock
1712         LockSupport.unpark(fiber2);
1713         while (holdsLock.get()) {
1714             Thread.sleep(20);
1715         }
1716     }
1717 
1718 
1719     // -- GC --
1720 
1721     // ensure that a Fiber can be GC"ed
1722     public void testGC1() {
1723         waitUntilObjectGCed(FiberScope.background().schedule(DO_NOTHING));
1724     }
1725 
1726     // ensure that a parked Fiber can be GC'ed
1727     public void testGC2() {
1728         waitUntilObjectGCed(FiberScope.background().schedule(() -> LockSupport.park()));
1729     }
1730 
1731     // ensure that a terminated Fiber can be GC'ed
1732     public void testGC3() throws Exception {
1733         var fiber = FiberScope.background().schedule(DO_NOTHING);
1734         fiber.join();
1735         var ref = new WeakReference<Fiber>(fiber);
1736         fiber = null;
1737         waitUntilObjectGCed(ref.get());
1738     }
1739 
1740     // waits for the given objecty to be GC'ed
1741     private static void waitUntilObjectGCed(Object obj) {
1742         var ref = new WeakReference<Object>(obj);
1743         obj = null;
1744         do {
1745             System.gc();
1746             try { Thread.sleep(50); } catch (InterruptedException e) { }
1747         } while (ref.get() != null);
1748     }
1749 
1750     // -- supporting code --
1751 
1752     private <T> T waitForValue(AtomicReference<T> ref) {
1753         T obj;
1754         boolean interrupted = false;
1755         while ((obj = ref.get()) == null) {
1756             try {
1757                 Thread.sleep(20);
1758             } catch (InterruptedException e) {
1759                 interrupted = true;
1760             }
1761         }
1762         if (interrupted)
1763             Thread.currentThread().interrupt();
1764         return obj;
1765     }
1766 
1767     static class Interrupter implements Runnable {
1768         final Thread thread;
1769         final long delay;
1770 
1771         Interrupter(Thread thread, long delay) {
1772             this.thread = thread;
1773             this.delay = delay;
1774         }
1775 
1776         static void schedule(Thread thread, long delay) {
1777             Interrupter task  = new Interrupter(thread, delay);
1778             new Thread(task).start();
1779         }
1780 
1781         @Override
1782         public void run() {
1783             try {
1784                 Thread.sleep(delay);
1785                 thread.interrupt();
1786             } catch (Exception e) {
1787                 e.printStackTrace();
1788             }
1789         }
1790     }
1791 }