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 Thread.sleep(1000); // give time for thread to park
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 }