1 /* 2 * Copyright (c) 2023, 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 * @summary Test virtual thread monitor enter/exit 27 * @key randomness 28 * @modules java.base/java.lang:+open 29 * @library /test/lib 30 * @run junit/othervm --enable-native-access=ALL-UNNAMED Monitors 31 */ 32 33 import java.time.Duration; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.concurrent.CountDownLatch; 37 import java.util.concurrent.ThreadFactory; 38 import java.util.concurrent.ThreadLocalRandom; 39 import java.util.concurrent.Executors; 40 import java.util.concurrent.ExecutorService; 41 import java.util.concurrent.atomic.AtomicBoolean; 42 import java.util.concurrent.locks.LockSupport; 43 44 import jdk.test.lib.thread.VThreadRunner; 45 import jdk.test.lib.thread.VThreadPinner; 46 import org.junit.jupiter.api.Disabled; 47 import org.junit.jupiter.api.Test; 48 import static org.junit.jupiter.api.Assertions.*; 49 import static org.junit.jupiter.api.Assumptions.*; 50 51 class Monitors { 52 // change this to availableProcessors() * 4 when monitor pining issue resolved 53 static final int MAX_VTHREAD_COUNT = Runtime.getRuntime().availableProcessors(); 54 55 // change this to 256 when monitor pining issue resolved 56 static final int MAX_ENTER_DEPTH = MAX_VTHREAD_COUNT - 1; 57 58 static ThreadFactory randomThreadFactory() { 59 return ThreadLocalRandom.current().nextBoolean() 60 ? Thread.ofPlatform().factory() 61 : Thread.ofVirtual().factory(); 62 } 63 64 /** 65 * Test monitor enter with no contention. 66 */ 67 @Test 68 void testEnterNoContention() throws Exception { 69 var lock = new Object(); 70 VThreadRunner.run(() -> { 71 assertFalse(Thread.holdsLock(lock)); 72 synchronized (lock) { 73 assertTrue(Thread.holdsLock(lock)); 74 } 75 assertFalse(Thread.holdsLock(lock)); 76 }); 77 } 78 79 /** 80 * Test monitor enter where monitor is held by platform thread. 81 */ 82 @Test 83 void testEnterWithContention() throws Exception { 84 var lock = new Object(); 85 var started = new CountDownLatch(1); 86 var entered = new AtomicBoolean(); 87 var vthread = Thread.ofVirtual().unstarted(() -> { 88 started.countDown(); 89 synchronized (lock) { 90 entered.set(true); 91 } 92 }); 93 try { 94 synchronized (lock) { 95 vthread.start(); 96 97 // wait for thread to start and block 98 started.await(); 99 await(vthread, Thread.State.BLOCKED); 100 } 101 } finally { 102 vthread.join(); 103 } 104 assertTrue(entered.get()); 105 } 106 107 /** 108 * Test monitor enter where monitor is held by virtual thread. 109 */ 110 @Disabled(value="Disabled due to pinning") 111 @Test 112 void testEnterWithContention2() throws Exception { 113 VThreadRunner.run(this::testEnterWithContention); 114 } 115 116 /** 117 * Test monitor reenter. 118 */ 119 @Test 120 void testReenter() throws Exception { 121 var lock = new Object(); 122 VThreadRunner.run(() -> { 123 assertFalse(Thread.holdsLock(lock)); 124 testReenter(lock, 0); 125 assertFalse(Thread.holdsLock(lock)); 126 }); 127 } 128 129 private void testReenter(Object lock, int depth) { 130 if (depth < MAX_ENTER_DEPTH) { 131 synchronized (lock) { 132 assertTrue(Thread.holdsLock(lock)); 133 testReenter(lock, depth + 1); 134 assertTrue(Thread.holdsLock(lock)); 135 } 136 } 137 } 138 139 /** 140 * Test monitor reenter when there are threads blocked trying to enter the monitor. 141 */ 142 @Test 143 void testReenterWithContention() throws Exception { 144 var lock = new Object(); 145 VThreadRunner.run(() -> { 146 List<Thread> threads = new ArrayList<>(); 147 testReenter(lock, 0, threads); 148 149 // wait for threads to terminate 150 for (Thread vthread : threads) { 151 vthread.join(); 152 } 153 }); 154 } 155 156 private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception { 157 if (depth < MAX_ENTER_DEPTH) { 158 synchronized (lock) { 159 assertTrue(Thread.holdsLock(lock)); 160 161 // start thread that blocks waiting to enter 162 var started = new CountDownLatch(1); 163 var thread = randomThreadFactory().newThread(() -> { 164 started.countDown(); 165 synchronized (lock) { 166 /* do nothing */ 167 } 168 }); 169 thread.start(); 170 171 // wait for thread to start and block 172 started.await(); 173 await(thread, Thread.State.BLOCKED); 174 threads.add(thread); 175 176 // test reenter 177 testReenter(lock, depth + 1, threads); 178 } 179 } 180 } 181 182 /** 183 * Test monitor enter when pinned. 184 */ 185 @Test 186 void testEnterWhenPinned() throws Exception { 187 VThreadRunner.run(() -> { 188 var lock = new Object(); 189 VThreadPinner.runPinned(() -> { 190 synchronized (lock) { 191 assertTrue(Thread.holdsLock(lock)); 192 } 193 }); 194 assertFalse(Thread.holdsLock(lock)); 195 }); 196 } 197 198 /** 199 * Test monitor reenter when pinned. 200 */ 201 @Test 202 void testReenterWhenPinned() throws Exception { 203 VThreadRunner.run(() -> { 204 var lock = new Object(); 205 synchronized (lock) { 206 VThreadPinner.runPinned(() -> { 207 assertTrue(Thread.holdsLock(lock)); 208 synchronized (lock) { 209 assertTrue(Thread.holdsLock(lock)); 210 } 211 assertTrue(Thread.holdsLock(lock)); 212 }); 213 } 214 assertFalse(Thread.holdsLock(lock)); 215 }); 216 } 217 218 /** 219 * Test contended monitor enter when pinned. Monitor is held by platform thread. 220 */ 221 @Test 222 void testContendedMonitorEnterWhenPinned() throws Exception { 223 var lock = new Object(); 224 var started = new CountDownLatch(1); 225 var entered = new AtomicBoolean(); 226 Thread vthread = Thread.ofVirtual().unstarted(() -> { 227 VThreadPinner.runPinned(() -> { 228 started.countDown(); 229 synchronized (lock) { 230 entered.set(true); 231 } 232 }); 233 }); 234 synchronized (lock) { 235 // start thread and wait for it to block 236 vthread.start(); 237 started.await(); 238 await(vthread, Thread.State.BLOCKED); 239 } 240 vthread.join(); 241 242 // check thread entered monitor 243 assertTrue(entered.get()); 244 } 245 246 /** 247 * Test contended monitor enter when pinned. Monitor is held by virtual thread. 248 */ 249 @Disabled(value="Disabled due to pinning") 250 @Test 251 void testContendedMonitorEnterWhenPinned2() throws Exception { 252 VThreadRunner.run(this::testContendedMonitorEnterWhenPinned); 253 } 254 255 /** 256 * Test that parking while holding a monitor releases the carrier. 257 */ 258 @Disabled(value="Disabled due to pinning") 259 @Test 260 void testReleaseWhenParked() throws Exception { 261 assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers"); 262 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { 263 Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); 264 265 var lock = new Object(); 266 267 // thread enters monitor and parks 268 var started = new CountDownLatch(1); 269 var vthread1 = builder.start(() -> { 270 started.countDown(); 271 synchronized (lock) { 272 LockSupport.park(); 273 } 274 }); 275 276 try { 277 // wait for thread to start and park 278 started.await(); 279 await(vthread1, Thread.State.WAITING); 280 281 // carrier should be released, use it for another thread 282 var executed = new AtomicBoolean(); 283 var vthread2 = builder.start(() -> { 284 executed.set(true); 285 }); 286 vthread2.join(); 287 assertTrue(executed.get()); 288 } finally { 289 LockSupport.unpark(vthread1); 290 vthread1.join(); 291 } 292 } 293 } 294 295 /** 296 * Test that blocked waiting to enter a monitor releases the carrier. 297 */ 298 @Disabled(value="Disabled due to pinning") 299 @Test 300 void testReleaseWhenBlocked() throws Exception { 301 assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers"); 302 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { 303 Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler); 304 305 var lock = new Object(); 306 307 // thread enters monitor 308 var started = new CountDownLatch(1); 309 var vthread1 = builder.unstarted(() -> { 310 started.countDown(); 311 synchronized (lock) { 312 } 313 }); 314 315 try { 316 synchronized (lock) { 317 // start thread and wait for it to block 318 vthread1.start(); 319 started.await(); 320 await(vthread1, Thread.State.BLOCKED); 321 322 // carrier should be released, use it for another thread 323 var executed = new AtomicBoolean(); 324 var vthread2 = builder.start(() -> { 325 executed.set(true); 326 }); 327 vthread2.join(); 328 assertTrue(executed.get()); 329 } 330 } finally { 331 vthread1.join(); 332 } 333 } 334 } 335 336 /** 337 * Test lots of virtual threads parked while holding a monitor. If the number of 338 * virtual threads exceeds the number of carrier threads then this test will hang if 339 * carriers aren't released. 340 */ 341 @Test 342 void testManyParkedThreads() throws Exception { 343 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 344 var done = new AtomicBoolean(); 345 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 346 var lock = new Object(); 347 var started = new CountDownLatch(1); 348 var vthread = Thread.ofVirtual().start(() -> { 349 started.countDown(); 350 synchronized (lock) { 351 while (!done.get()) { 352 LockSupport.park(); 353 } 354 } 355 }); 356 // wait for thread to start and park 357 started.await(); 358 await(vthread, Thread.State.WAITING); 359 vthreads[i] = vthread; 360 } 361 362 // cleanup 363 done.set(true); 364 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 365 var vthread = vthreads[i]; 366 LockSupport.unpark(vthread); 367 vthread.join(); 368 } 369 } 370 371 /** 372 * Test lots of virtual threads blocked waiting to enter a monitor. If the number 373 * of virtual threads exceeds the number of carrier threads this test will hang if 374 * carriers aren't released. 375 */ 376 @Test 377 void testManyBlockedThreads() throws Exception { 378 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 379 var lock = new Object(); 380 synchronized (lock) { 381 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 382 var started = new CountDownLatch(1); 383 var vthread = Thread.ofVirtual().start(() -> { 384 started.countDown(); 385 synchronized (lock) { 386 } 387 }); 388 // wait for thread to start and block 389 started.await(); 390 await(vthread, Thread.State.BLOCKED); 391 vthreads[i] = vthread; 392 } 393 } 394 395 // cleanup 396 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 397 vthreads[i].join(); 398 } 399 } 400 401 /** 402 * Test that unblocking a virtual thread waiting to enter a monitor does not consume 403 * the thread's parking permit. 404 */ 405 @Test 406 void testParkingPermitNotConsumed() throws Exception { 407 var lock = new Object(); 408 var started = new CountDownLatch(1); 409 var vthread = Thread.ofVirtual().unstarted(() -> { 410 started.countDown(); 411 LockSupport.unpark(Thread.currentThread()); 412 synchronized (lock) { } // should block 413 LockSupport.park(); // should not park 414 }); 415 416 synchronized (lock) { 417 vthread.start(); 418 // wait for thread to start and block 419 started.await(); 420 await(vthread, Thread.State.BLOCKED); 421 } 422 vthread.join(); 423 } 424 425 /** 426 * Test that unblocking a virtual thread waiting to enter a monitor does not make 427 * available the thread's parking permit. 428 */ 429 @Test 430 void testParkingPermitNotOffered() throws Exception { 431 var lock = new Object(); 432 var started = new CountDownLatch(1); 433 var vthread = Thread.ofVirtual().unstarted(() -> { 434 started.countDown(); 435 synchronized (lock) { } // should block 436 LockSupport.park(); // should park 437 }); 438 439 synchronized (lock) { 440 vthread.start(); 441 // wait for thread to start and block 442 started.await(); 443 await(vthread, Thread.State.BLOCKED); 444 } 445 446 try { 447 // wait for thread to park, it should not terminate 448 await(vthread, Thread.State.WAITING); 449 vthread.join(Duration.ofMillis(100)); 450 assertEquals(Thread.State.WAITING, vthread.getState()); 451 } finally { 452 LockSupport.unpark(vthread); 453 vthread.join(); 454 } 455 } 456 457 /** 458 * Wait for the given thread to reach the given state. 459 */ 460 private void await(Thread thread, Thread.State expectedState) { 461 Thread.State state = thread.getState(); 462 while (state != expectedState) { 463 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 464 Thread.yield(); 465 state = thread.getState(); 466 } 467 } 468 }