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 }