1 /* 2 * Copyright (c) 2023, 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 with a synchronized native method and a native method 27 * that enter/exits a monitor with JNI MonitorEnter/MonitorExit 28 * @requires vm.continuations 29 * @modules java.base/java.lang:+open 30 * @library /test/lib 31 * @run junit/othervm SynchronizedNative 32 */ 33 34 /* 35 * @test id=Xint 36 * @requires vm.continuations 37 * @modules java.base/java.lang:+open 38 * @library /test/lib 39 * @run junit/othervm -Xint SynchronizedNative 40 */ 41 42 /* 43 * @test id=Xcomp-TieredStopAtLevel1 44 * @requires vm.continuations 45 * @modules java.base/java.lang:+open 46 * @library /test/lib 47 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 SynchronizedNative 48 */ 49 50 /* 51 * @test id=Xcomp-noTieredCompilation 52 * @requires vm.continuations 53 * @modules java.base/java.lang:+open 54 * @library /test/lib 55 * @run junit/othervm -Xcomp -XX:-TieredCompilation SynchronizedNative 56 */ 57 58 import java.util.concurrent.CountDownLatch; 59 import java.util.concurrent.Executors; 60 import java.util.concurrent.ExecutorService; 61 import java.util.concurrent.Phaser; 62 import java.util.concurrent.ThreadFactory; 63 import java.util.concurrent.atomic.AtomicBoolean; 64 import java.util.concurrent.locks.LockSupport; 65 import java.util.stream.IntStream; 66 import java.util.stream.Stream; 67 68 import jdk.test.lib.thread.VThreadPinner; 69 import jdk.test.lib.thread.VThreadRunner; 70 import jdk.test.lib.thread.VThreadScheduler; 71 72 import org.junit.jupiter.api.Test; 73 import org.junit.jupiter.api.AfterAll; 74 import org.junit.jupiter.api.BeforeAll; 75 import org.junit.jupiter.params.ParameterizedTest; 76 import org.junit.jupiter.params.provider.Arguments; 77 import org.junit.jupiter.params.provider.MethodSource; 78 import static org.junit.jupiter.api.Assertions.*; 79 80 class SynchronizedNative { 81 private static int initialParallelism; 82 83 @BeforeAll 84 static void setup() throws Exception { 85 // need at least two carriers when main thread is a virtual thread 86 if (Thread.currentThread().isVirtual()) { 87 initialParallelism = VThreadRunner.ensureParallelism(2); 88 } 89 System.loadLibrary("SynchronizedNative"); 90 } 91 92 @AfterAll 93 static void finish() { 94 // restore parallelism if needed 95 if (initialParallelism > 0) { 96 VThreadRunner.setParallelism(initialParallelism); 97 } 98 } 99 100 /** 101 * Test entering a monitor with a synchronized native method, no contention. 102 */ 103 @Test 104 void testEnter() throws Exception { 105 Object lock = this; 106 VThreadRunner.run(() -> { 107 runWithSynchronizedNative(() -> { 108 assertTrue(Thread.holdsLock(lock)); 109 }); 110 assertFalse(Thread.holdsLock(lock)); 111 }); 112 } 113 114 /** 115 * Test reentering a monitor with synchronized native method, no contention. 116 */ 117 @Test 118 void testReenter() throws Exception { 119 Object lock = this; 120 VThreadRunner.run(() -> { 121 122 // enter, reenter with a synchronized native method 123 synchronized (lock) { 124 runWithSynchronizedNative(() -> { 125 assertTrue(Thread.holdsLock(lock)); 126 }); 127 assertTrue(Thread.holdsLock(lock)); 128 } 129 assertFalse(Thread.holdsLock(lock)); 130 131 // enter with synchronized native method, renter with synchronized statement 132 runWithSynchronizedNative(() -> { 133 assertTrue(Thread.holdsLock(lock)); 134 synchronized (lock) { 135 assertTrue(Thread.holdsLock(lock)); 136 } 137 assertTrue(Thread.holdsLock(lock)); 138 }); 139 assertFalse(Thread.holdsLock(lock)); 140 141 // enter with synchronized native method, reenter with synchronized native method 142 runWithSynchronizedNative(() -> { 143 assertTrue(Thread.holdsLock(lock)); 144 runWithSynchronizedNative(() -> { 145 assertTrue(Thread.holdsLock(lock)); 146 }); 147 assertTrue(Thread.holdsLock(lock)); 148 }); 149 assertFalse(Thread.holdsLock(lock)); 150 }); 151 } 152 153 /** 154 * Test entering a monitor with a synchronized native method and with contention. 155 */ 156 @Test 157 void testEnterWithContention() throws Exception { 158 var lock = this; 159 var started = new CountDownLatch(1); 160 var entered = new AtomicBoolean(); 161 var vthread = Thread.ofVirtual().unstarted(() -> { 162 started.countDown(); 163 runWithSynchronizedNative(() -> { 164 assertTrue(Thread.holdsLock(lock)); 165 entered.set(true); 166 }); 167 }); 168 try { 169 synchronized (lock) { 170 vthread.start(); 171 172 // wait for thread to start and block 173 started.await(); 174 await(vthread, Thread.State.BLOCKED); 175 176 assertFalse(entered.get()); 177 } 178 } finally { 179 vthread.join(); 180 } 181 assertTrue(entered.get()); 182 } 183 184 /** 185 * Returns a stream of elements that are ordered pairs of platform and virtual thread 186 * counts. 0,2,4 platform threads. 2,4,6,8 virtual threads. 187 */ 188 static Stream<Arguments> threadCounts() { 189 return IntStream.range(0, 5) 190 .filter(i -> i % 2 == 0) 191 .mapToObj(i -> i) 192 .flatMap(np -> IntStream.range(2, 9) 193 .filter(i -> i % 2 == 0) 194 .mapToObj(vp -> Arguments.of(np, vp))); 195 } 196 197 /** 198 * Execute a task concurrently from both platform and virtual threads. 199 */ 200 private void executeConcurrently(int nPlatformThreads, 201 int nVirtualThreads, 202 Runnable task) throws Exception { 203 int parallism = nVirtualThreads; 204 if (Thread.currentThread().isVirtual()) { 205 parallism++; 206 } 207 int previousParallelism = VThreadRunner.ensureParallelism(parallism); 208 try { 209 int nthreads = nPlatformThreads + nVirtualThreads; 210 var phaser = new Phaser(nthreads + 1); 211 212 // start all threads 213 var threads = new Thread[nthreads]; 214 int index = 0; 215 for (int i = 0; i < nPlatformThreads; i++) { 216 threads[index++] = Thread.ofPlatform().start(() -> { 217 phaser.arriveAndAwaitAdvance(); 218 task.run(); 219 }); 220 } 221 for (int i = 0; i < nVirtualThreads; i++) { 222 threads[index++] = Thread.ofVirtual().start(() -> { 223 phaser.arriveAndAwaitAdvance(); 224 task.run(); 225 }); 226 } 227 228 // wait for all threads to start 229 phaser.arriveAndAwaitAdvance(); 230 System.err.printf(" %d threads started%n", nthreads); 231 232 // wait for all threads to terminate 233 for (Thread thread : threads) { 234 if (thread != null) { 235 System.err.printf(" join %s ...%n", thread); 236 thread.join(); 237 } 238 } 239 } finally { 240 // reset parallelism 241 VThreadRunner.setParallelism(previousParallelism); 242 } 243 } 244 245 246 /** 247 * Test entering a monitor with a synchronized native method from many threads 248 * at the same time. 249 */ 250 @ParameterizedTest 251 @MethodSource("threadCounts") 252 void testEnterConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception { 253 var counter = new Object() { 254 int value; 255 int value() { return value; } 256 void increment() { value++; } 257 }; 258 var lock = this; 259 executeConcurrently(nPlatformThreads, nVirtualThreads, () -> { 260 runWithSynchronizedNative(() -> { 261 assertTrue(Thread.holdsLock(lock)); 262 counter.increment(); 263 LockSupport.parkNanos(100_000_000); // 100ms 264 }); 265 }); 266 synchronized (lock) { 267 assertEquals(nPlatformThreads + nVirtualThreads, counter.value()); 268 } 269 } 270 271 /** 272 * Test entering a monitor with JNI MonitorEnter. 273 */ 274 @Test 275 void testEnterInNative() throws Exception { 276 Object lock = new Object(); 277 VThreadRunner.run(() -> { 278 runWithMonitorEnteredInNative(lock, () -> { 279 assertTrue(Thread.holdsLock(lock)); 280 }); 281 assertFalse(Thread.holdsLock(lock)); 282 }); 283 } 284 285 /** 286 * Test reentering a monitor with JNI MonitorEnter. 287 */ 288 @Test 289 void testReenterInNative() throws Exception { 290 Object lock = new Object(); 291 VThreadRunner.run(() -> { 292 293 // enter, reenter with JNI MonitorEnter 294 synchronized (lock) { 295 runWithMonitorEnteredInNative(lock, () -> { 296 assertTrue(Thread.holdsLock(lock)); 297 }); 298 assertTrue(Thread.holdsLock(lock)); 299 } 300 assertFalse(Thread.holdsLock(lock)); 301 302 // enter with JNI MonitorEnter, renter with synchronized statement 303 runWithMonitorEnteredInNative(lock, () -> { 304 assertTrue(Thread.holdsLock(lock)); 305 synchronized (lock) { 306 assertTrue(Thread.holdsLock(lock)); 307 } 308 assertTrue(Thread.holdsLock(lock)); 309 }); 310 assertFalse(Thread.holdsLock(lock)); 311 312 // enter with JNI MonitorEnter, renter with JNI MonitorEnter 313 runWithMonitorEnteredInNative(lock, () -> { 314 assertTrue(Thread.holdsLock(lock)); 315 runWithMonitorEnteredInNative(lock, () -> { 316 assertTrue(Thread.holdsLock(lock)); 317 }); 318 assertTrue(Thread.holdsLock(lock)); 319 }); 320 assertFalse(Thread.holdsLock(lock)); 321 }); 322 } 323 324 /** 325 * Test entering a monitor with JNI MonitorEnter and with contention. 326 */ 327 @Test 328 void testEnterInNativeWithContention() throws Exception { 329 var lock = new Object(); 330 var started = new CountDownLatch(1); 331 var entered = new AtomicBoolean(); 332 var vthread = Thread.ofVirtual().unstarted(() -> { 333 started.countDown(); 334 runWithMonitorEnteredInNative(lock, () -> { 335 assertTrue(Thread.holdsLock(lock)); 336 entered.set(true); 337 }); 338 }); 339 try { 340 synchronized (lock) { 341 vthread.start(); 342 343 // wait for thread to start and block 344 started.await(); 345 await(vthread, Thread.State.BLOCKED); 346 347 assertFalse(entered.get()); 348 } 349 } finally { 350 vthread.join(); 351 } 352 assertTrue(entered.get()); 353 } 354 355 /** 356 * Test entering a monitor with JNI MonitorEnter from many threads at the same time. 357 */ 358 @ParameterizedTest 359 @MethodSource("threadCounts") 360 void testEnterInNativeConcurrently(int nPlatformThreads, int nVirtualThreads) throws Exception { 361 var counter = new Object() { 362 int value; 363 int value() { return value; } 364 void increment() { value++; } 365 }; 366 var lock = counter; 367 executeConcurrently(nPlatformThreads, nVirtualThreads, () -> { 368 runWithMonitorEnteredInNative(lock, () -> { 369 assertTrue(Thread.holdsLock(lock)); 370 counter.increment(); 371 LockSupport.parkNanos(100_000_000); // 100ms 372 }); 373 }); 374 synchronized (lock) { 375 assertEquals(nPlatformThreads + nVirtualThreads, counter.value()); 376 } 377 } 378 379 /** 380 * Test parking with synchronized native method on stack. 381 */ 382 @Test 383 void testParkingWhenPinned() throws Exception { 384 var lock = this; 385 var started = new CountDownLatch(1); 386 var entered = new AtomicBoolean(); 387 var done = new AtomicBoolean(); 388 var vthread = Thread.ofVirtual().start(() -> { 389 started.countDown(); 390 runWithSynchronizedNative(() -> { 391 assertTrue(Thread.holdsLock(lock)); 392 entered.set(true); 393 while (!done.get()) { 394 LockSupport.park(); 395 } 396 }); 397 }); 398 try { 399 // wait for thread to start and block 400 started.await(); 401 await(vthread, Thread.State.WAITING); 402 } finally { 403 done.set(true); 404 LockSupport.unpark(vthread); 405 vthread.join(); 406 } 407 assertTrue(entered.get()); 408 } 409 410 /** 411 * Test blocking with synchronized native method on stack. 412 */ 413 @Test 414 void testBlockingWhenPinned() throws Exception { 415 var lock1 = this; 416 var lock2 = new Object(); 417 418 var started = new CountDownLatch(1); 419 var entered1 = new AtomicBoolean(); // set to true when vthread enters lock1 420 var entered2 = new AtomicBoolean(); // set to true when vthread enters lock2 421 422 var vthread = Thread.ofVirtual().unstarted(() -> { 423 started.countDown(); 424 runWithSynchronizedNative(() -> { 425 assertTrue(Thread.holdsLock(lock1)); 426 entered1.set(true); 427 synchronized (lock2) { // should block 428 assertTrue(Thread.holdsLock(lock2)); 429 entered2.set(true); 430 } 431 }); 432 }); 433 try { 434 synchronized (lock2) { 435 // start thread and wait for it to block trying to enter lock2 436 vthread.start(); 437 started.await(); 438 await(vthread, Thread.State.BLOCKED); 439 440 assertTrue(entered1.get()); 441 assertFalse(entered2.get()); 442 } 443 } finally { 444 vthread.join(); 445 } 446 assertTrue(entered2.get()); 447 } 448 449 /** 450 * Test that blocking on synchronized native method releases the carrier. 451 */ 452 //@Test 453 void testReleaseWhenBlocked() throws Exception { 454 assertTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers"); 455 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) { 456 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler); 457 458 var lock = this; 459 var started = new CountDownLatch(1); 460 var entered = new AtomicBoolean(); // set to true when vthread enters lock 461 462 var vthread1 = factory.newThread(() -> { 463 started.countDown(); 464 runWithSynchronizedNative(() -> { 465 assertTrue(Thread.holdsLock(lock)); 466 entered.set(true); 467 }); 468 assertFalse(Thread.holdsLock(lock)); 469 }); 470 471 vthread1.start(); 472 try { 473 synchronized (this) { 474 // start thread and wait for it to block 475 vthread1.start(); 476 started.await(); 477 await(vthread1, Thread.State.BLOCKED); 478 479 // carrier should be released, use it for another thread 480 var executed = new AtomicBoolean(); 481 var vthread2 = factory.newThread(() -> { 482 executed.set(true); 483 }); 484 vthread2.start(); 485 vthread2.join(); 486 assertTrue(executed.get()); 487 } 488 } finally { 489 vthread1.join(); 490 } 491 } 492 } 493 494 /** 495 * Invokes the given task's run method while holding the monitor for "this". 496 */ 497 private synchronized native void runWithSynchronizedNative(Runnable task); 498 499 /** 500 * Invokes the given task's run method while holding the monitor for the given 501 * object. The monitor is entered with JNI MonitorEnter, and exited with JNI MonitorExit. 502 */ 503 private native void runWithMonitorEnteredInNative(Object lock, Runnable task); 504 505 /** 506 * Called from native methods to run the given task. 507 */ 508 private void run(Runnable task) { 509 task.run(); 510 } 511 512 /** 513 * Waits for the given thread to reach a given state. 514 */ 515 private void await(Thread thread, Thread.State expectedState) throws InterruptedException { 516 Thread.State state = thread.getState(); 517 while (state != expectedState) { 518 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 519 Thread.sleep(10); 520 state = thread.getState(); 521 } 522 } 523 }