1 /*
  2  * Copyright (c) 2019, 2025, 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=default
 26  * @summary Test virtual threads using park/unpark
 27  * @modules java.base/java.lang:+open jdk.management
 28  * @library /test/lib
 29  * @run junit/timeout=480 Parking
 30  */
 31 
 32 /*
 33  * @test id=Xint
 34  * @modules java.base/java.lang:+open jdk.management
 35  * @library /test/lib
 36  * @run junit/othervm/timeout=480 -Xint Parking
 37  */
 38 
 39 /*
 40  * @test id=Xcomp
 41  * @modules java.base/java.lang:+open jdk.management
 42  * @library /test/lib
 43  * @run junit/othervm/timeout=480 -Xcomp Parking
 44  */
 45 
 46 /*
 47  * @test id=Xcomp-noTieredCompilation
 48  * @modules java.base/java.lang:+open jdk.management
 49  * @library /test/lib
 50  * @run junit/othervm/timeout=480 -Xcomp -XX:-TieredCompilation Parking
 51  */
 52 
 53 import java.time.Duration;
 54 import java.util.concurrent.Executors;
 55 import java.util.concurrent.ExecutorService;
 56 import java.util.concurrent.CountDownLatch;
 57 import java.util.concurrent.ThreadFactory;
 58 import java.util.concurrent.TimeUnit;
 59 import java.util.concurrent.atomic.AtomicBoolean;
 60 import java.util.concurrent.locks.LockSupport;
 61 
 62 import jdk.test.lib.thread.VThreadRunner;
 63 import jdk.test.lib.thread.VThreadScheduler;
 64 import org.junit.jupiter.api.Test;
 65 import org.junit.jupiter.params.ParameterizedTest;
 66 import org.junit.jupiter.params.provider.ValueSource;
 67 import static org.junit.jupiter.api.Assertions.*;
 68 import static org.junit.jupiter.api.Assumptions.*;
 69 
 70 class Parking {
 71     static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
 72     static final Object lock = new Object();
 73 
 74     /**
 75      * Park, unparked by platform thread.
 76      */
 77     @Test
 78     void testPark1() throws Exception {
 79         var thread = Thread.ofVirtual().start(LockSupport::park);
 80         Thread.sleep(1000); // give time for virtual thread to park
 81         LockSupport.unpark(thread);
 82         thread.join();
 83     }
 84 
 85     /**
 86      * Park, unparked by virtual thread.
 87      */
 88     @Test
 89     void testPark2() throws Exception {
 90         var thread1 = Thread.ofVirtual().start(LockSupport::park);
 91         Thread.sleep(1000); // give time for virtual thread to park
 92         var thread2 = Thread.ofVirtual().start(() -> LockSupport.unpark(thread1));
 93         thread1.join();
 94         thread2.join();
 95     }
 96 
 97     /**
 98      * Park while holding monitor, unparked by platform thread.
 99      */
100     @Test
101     void testPark3() throws Exception {
102         var thread = Thread.ofVirtual().start(() -> {
103             synchronized (lock) {
104                 LockSupport.park();
105             }
106         });
107         Thread.sleep(1000); // give time for virtual thread to park
108         LockSupport.unpark(thread);
109         thread.join();
110     }
111 
112     /**
113      * Park with native frame on stack.
114      */
115     @Test
116     void testPark4() throws Exception {
117         // not implemented
118     }
119 
120     /**
121      * Unpark before park.
122      */
123     @Test
124     void testPark5() throws Exception {
125         var thread = Thread.ofVirtual().start(() -> {
126             LockSupport.unpark(Thread.currentThread());
127             LockSupport.park();
128         });
129         thread.join();
130     }
131 
132     /**
133      * 2 x unpark before park.
134      */
135     @Test
136     void testPark6() throws Exception {
137         var thread = Thread.ofVirtual().start(() -> {
138             Thread me = Thread.currentThread();
139             LockSupport.unpark(me);
140             LockSupport.unpark(me);
141             LockSupport.park();
142             LockSupport.park();  // should park
143         });
144         await(thread, Thread.State.WAITING);
145         LockSupport.unpark(thread);
146         thread.join();
147     }
148 
149     /**
150      * 2 x park and unpark by platform thread.
151      */
152     @Test
153     void testPark7() throws Exception {
154         var thread = Thread.ofVirtual().start(() -> {
155             LockSupport.park();
156             LockSupport.park();
157         });
158 
159         Thread.sleep(1000); // give time for thread to park
160 
161         // unpark, virtual thread should park again
162         LockSupport.unpark(thread);
163         Thread.sleep(1000);
164         assertTrue(thread.isAlive());
165 
166         // let it terminate
167         LockSupport.unpark(thread);
168         thread.join();
169     }
170 
171     /**
172      * Park with interrupted status set.
173      */
174     @Test
175     void testPark8() throws Exception {
176         VThreadRunner.run(() -> {
177             Thread t = Thread.currentThread();
178             t.interrupt();
179             LockSupport.park();
180             assertTrue(t.isInterrupted());
181         });
182     }
183 
184     /**
185      * Thread interrupt when parked.
186      */
187     @Test
188     void testPark9() throws Exception {
189         VThreadRunner.run(() -> {
190             Thread t = Thread.currentThread();
191             scheduleInterrupt(t, 1000);
192             while (!Thread.currentThread().isInterrupted()) {
193                 LockSupport.park();
194             }
195         });
196     }
197 
198     /**
199      * Park while holding monitor and with interrupted status set.
200      */
201     @Test
202     void testPark10() throws Exception {
203         VThreadRunner.run(() -> {
204             Thread t = Thread.currentThread();
205             t.interrupt();
206             synchronized (lock) {
207                 LockSupport.park();
208             }
209             assertTrue(t.isInterrupted());
210         });
211     }
212 
213     /**
214      * Thread interrupt when parked while holding monitor
215      */
216     @Test
217     void testPark11() throws Exception {
218         VThreadRunner.run(() -> {
219             Thread t = Thread.currentThread();
220             scheduleInterrupt(t, 1000);
221             while (!Thread.currentThread().isInterrupted()) {
222                 synchronized (lock) {
223                     LockSupport.park();
224                 }
225             }
226         });
227     }
228 
229     /**
230      * parkNanos(-1) completes immediately
231      */
232     @Test
233     void testParkNanos1() throws Exception {
234         VThreadRunner.run(() -> LockSupport.parkNanos(-1));
235     }
236 
237     /**
238      * parkNanos(0) completes immediately
239      */
240     @Test
241     void testParkNanos2() throws Exception {
242         VThreadRunner.run(() -> LockSupport.parkNanos(0));
243     }
244 
245     /**
246      * parkNanos(1000ms) parks thread.
247      */
248     @Test
249     void testParkNanos3() throws Exception {
250         VThreadRunner.run(() -> {
251             // park for 1000ms
252             long nanos = TimeUnit.NANOSECONDS.convert(1000, TimeUnit.MILLISECONDS);
253             long start = System.nanoTime();
254             LockSupport.parkNanos(nanos);
255 
256             // check that virtual thread parked for >= 900ms
257             long elapsed = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start,
258                     TimeUnit.NANOSECONDS);
259             assertTrue(elapsed >= 900);
260         });
261     }
262 
263     /**
264      * Park with parkNanos, unparked by platform thread.
265      */
266     @Test
267     void testParkNanos4() throws Exception {
268         var thread = Thread.ofVirtual().start(() -> {
269             long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
270             LockSupport.parkNanos(nanos);
271         });
272         Thread.sleep(100); // give time for virtual thread to park
273         LockSupport.unpark(thread);
274         thread.join();
275     }
276 
277     /**
278      * Park with parkNanos, unparked by virtual thread.
279      */
280     @Test
281     void testParkNanos5() throws Exception {
282         var thread1 = Thread.ofVirtual().start(() -> {
283             long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
284             LockSupport.parkNanos(nanos);
285         });
286         Thread.sleep(100);  // give time for virtual thread to park
287         var thread2 = Thread.ofVirtual().start(() -> LockSupport.unpark(thread1));
288         thread1.join();
289         thread2.join();
290     }
291 
292     /**
293      * Unpark before parkNanos.
294      */
295     @Test
296     void testParkNanos6() throws Exception {
297         VThreadRunner.run(() -> {
298             LockSupport.unpark(Thread.currentThread());
299             long nanos = TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
300             LockSupport.parkNanos(nanos);
301         });
302     }
303 
304     /**
305      * Unpark before parkNanos(0), should consume parking permit.
306      */
307     @Test
308     void testParkNanos7() throws Exception {
309         var thread = Thread.ofVirtual().start(() -> {
310             LockSupport.unpark(Thread.currentThread());
311             LockSupport.parkNanos(0);  // should consume parking permit
312             LockSupport.park();  // should block
313         });
314         boolean isAlive = thread.join(Duration.ofSeconds(2));
315         assertTrue(isAlive);
316         LockSupport.unpark(thread);
317         thread.join();
318     }
319 
320     /**
321      * Park with parkNanos and interrupted status set.
322      */
323     @Test
324     void testParkNanos8() throws Exception {
325         VThreadRunner.run(() -> {
326             Thread t = Thread.currentThread();
327             t.interrupt();
328             LockSupport.parkNanos(Duration.ofDays(1).toNanos());
329             assertTrue(t.isInterrupted());
330         });
331     }
332 
333     /**
334      * Thread interrupt when parked in parkNanos.
335      */
336     @Test
337     void testParkNanos9() throws Exception {
338         VThreadRunner.run(() -> {
339             Thread t = Thread.currentThread();
340             scheduleInterrupt(t, 1000);
341             while (!Thread.currentThread().isInterrupted()) {
342                 LockSupport.parkNanos(Duration.ofDays(1).toNanos());
343             }
344         });
345     }
346 
347     /**
348      * Park with parkNanos while holding monitor and with interrupted status set.
349      */
350     @Test
351     void testParkNanos10() throws Exception {
352         VThreadRunner.run(() -> {
353             Thread t = Thread.currentThread();
354             t.interrupt();
355             synchronized (lock) {
356                 LockSupport.parkNanos(Duration.ofDays(1).toNanos());
357             }
358             assertTrue(t.isInterrupted());
359         });
360     }
361 
362     /**
363      * Thread interrupt when parked in parkNanos and while holding monitor.
364      */
365     @Test
366     void testParkNanos11() throws Exception {
367         VThreadRunner.run(() -> {
368             Thread t = Thread.currentThread();
369             scheduleInterrupt(t, 1000);
370             while (!Thread.currentThread().isInterrupted()) {
371                 synchronized (lock) {
372                     LockSupport.parkNanos(Duration.ofDays(1).toNanos());
373                 }
374             }
375         });
376     }
377 
378     /**
379      * Test that parking while holding a monitor releases the carrier.
380      */
381     @ParameterizedTest
382     @ValueSource(booleans = { true, false })
383     void testParkWhenHoldingMonitor(boolean reenter) throws Exception {
384         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
385         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
386             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
387 
388             var lock = new Object();
389 
390             // thread enters (and maybe reenters) a monitor and parks
391             var started = new CountDownLatch(1);
392             var vthread1 = factory.newThread(() -> {
393                 started.countDown();
394                 synchronized (lock) {
395                     if (reenter) {
396                         synchronized (lock) {
397                             LockSupport.park();
398                         }
399                     } else {
400                         LockSupport.park();
401                     }
402                 }
403             });
404 
405             vthread1.start();
406             try {
407                 // wait for thread to start and park
408                 started.await();
409                 await(vthread1, Thread.State.WAITING);
410 
411                 // carrier should be released, use it for another thread
412                 var executed = new AtomicBoolean();
413                 var vthread2 = factory.newThread(() -> {
414                     executed.set(true);
415                 });
416                 vthread2.start();
417                 vthread2.join();
418                 assertTrue(executed.get());
419             } finally {
420                 LockSupport.unpark(vthread1);
421                 vthread1.join();
422             }
423         }
424     }
425 
426     /**
427      * Test lots of virtual threads parked while holding a monitor. If the number of
428      * virtual threads exceeds the number of carrier threads then this test will hang if
429      * parking doesn't release the carrier.
430      */
431     @Test
432     void testManyParkedWhenHoldingMonitor() throws Exception {
433         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
434         var done = new AtomicBoolean();
435         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
436             var lock = new Object();
437             var started = new CountDownLatch(1);
438             var vthread = Thread.ofVirtual().start(() -> {
439                 started.countDown();
440                 synchronized (lock) {
441                     while (!done.get()) {
442                         LockSupport.park();
443                     }
444                 }
445             });
446             // wait for thread to start and park
447             started.await();
448             await(vthread, Thread.State.WAITING);
449             vthreads[i] = vthread;
450         }
451 
452         // cleanup
453         done.set(true);
454         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
455             var vthread = vthreads[i];
456             LockSupport.unpark(vthread);
457             vthread.join();
458         }
459     }
460 
461     /**
462      * Schedule a thread to be interrupted after a delay.
463      */
464     private static void scheduleInterrupt(Thread thread, long delay) {
465         Runnable interruptTask = () -> {
466             try {
467                 Thread.sleep(delay);
468                 thread.interrupt();
469             } catch (Exception e) {
470                 e.printStackTrace();
471             }
472         };
473         new Thread(interruptTask).start();
474     }
475 
476     /**
477      * Waits for the given thread to reach a given state.
478      */
479     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
480         Thread.State state = thread.getState();
481         while (state != expectedState) {
482             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
483             Thread.sleep(10);
484             state = thread.getState();
485         }
486     }
487 }