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