1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test id=timeout
 26  * @summary Stress test timed-Object.wait
 27  * @run main/othervm TimedWaitALot 200
 28  */
 29 
 30 /*
 31  * @test id=timeout-notify
 32  * @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
 33  *     at around the same time that the timeout expires.
 34  * @run main/othervm TimedWaitALot 200 true false
 35  */
 36 
 37 /*
 38  * @test id=timeout-interrupt
 39  * @summary Test timed-Object.wait where the waiting thread is awakened with Thread.interrupt
 40  *     at around the same time that the timeout expires.
 41  * @run main/othervm TimedWaitALot 200 false true
 42  */
 43 
 44 /*
 45  * @test id=timeout-notify-interrupt
 46  * @summary Test timed-Object.wait where the waiting thread is awakened with Object.notify
 47  *     and Thread.interrupt at around the same time that the timeout expires.
 48  * @run main/othervm TimedWaitALot 100 true true
 49  */
 50 
 51 import java.time.Instant;
 52 import java.util.concurrent.Executors;
 53 import java.util.concurrent.SynchronousQueue;
 54 import java.util.concurrent.ThreadLocalRandom;
 55 
 56 public class TimedWaitALot {
 57     public static void main(String[] args) throws Exception {
 58         int iterations = Integer.parseInt(args[0]);
 59         boolean notify = args.length >= 2 && "true".equals(args[1]);
 60         boolean interrupt = args.length >=3 && "true".equals(args[2]);
 61 
 62         // test all timeouts concurrently
 63         int[] timeouts = { 10, 20, 50, 100 };
 64         for (int i = 1; i <= iterations; i++) {
 65             System.out.println(Instant.now() + " => " + i + " of " + iterations);
 66             test(notify, interrupt, timeouts);
 67         }
 68     }
 69 
 70     /**
 71      * Start a first virtual thread to wait in Object.wait(millis).
 72      * If {@code notify} is true, start a virtual thread to use Object.notifyAll at around
 73      * the same time that the timeout expires.
 74      * If {@code interrupt} is true, start virtual thread to interrupts the first virtual
 75      * thread at around the same time as the timeout expires.
 76      */
 77     static void test(boolean notify, boolean interrupt, int... timeouts) throws Exception {
 78         try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
 79             for (int timeout : timeouts) {
 80                 var queue = new SynchronousQueue<Thread>();
 81                 var lock = new Object();
 82 
 83                 // virtual thread waits with Object.wait(timeout)
 84                 executor.submit(() -> {
 85                     queue.put(Thread.currentThread());
 86                     synchronized (lock) {
 87                         lock.wait(timeout);
 88                     }
 89                     return null;
 90                 });
 91 
 92                 // wait for thread to start
 93                 Thread thread = queue.take();
 94 
 95                 // start thread to Object.notifyAll at around time that the timeout expires
 96                 if (notify) {
 97                     if (ThreadLocalRandom.current().nextBoolean()) {
 98                         synchronized (lock) {
 99                             sleepLessThan(timeout);
100                             lock.notifyAll();
101                         }
102                     } else {
103                         sleepLessThan(timeout);
104                         synchronized (lock) {
105                             lock.notifyAll();
106                         }
107                     }
108                 }
109 
110                 // start thread to interrupt first thread at around time that the timeout expires
111                 if (interrupt) {
112                     executor.submit(() -> {
113                         sleepLessThan(timeout);
114                         thread.interrupt();
115                         return null;
116                     });
117                 }
118             }
119         }
120     }
121 
122     /**
123      * Sleeps for just less than the given timeout, in millis.
124      */
125     private static void sleepLessThan(long timeout) throws InterruptedException {
126         int delta = ThreadLocalRandom.current().nextInt(10);
127         Thread.sleep(timeout - delta);
128     }
129 }