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