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