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 }