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 thread with monitor enter/exit
 27  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 28  * @modules java.base/java.lang:+open
 29  * @library /test/lib
 30  * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
 31  */
 32 
 33 /*
 34  * @test id=LM_LEGACY
 35  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 36  * @modules java.base/java.lang:+open
 37  * @library /test/lib
 38  * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 39  */
 40 
 41 /*
 42  * @test id=LM_LIGHTWEIGHT
 43  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 44  * @modules java.base/java.lang:+open
 45  * @library /test/lib
 46  * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 47  */
 48 
 49 /*
 50  * @test id=Xint-LM_LEGACY
 51  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 52  * @modules java.base/java.lang:+open
 53  * @library /test/lib
 54  * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 55  */
 56 
 57 /*
 58  * @test id=Xint-LM_LIGHTWEIGHT
 59  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 60  * @modules java.base/java.lang:+open
 61  * @library /test/lib
 62  * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 63  */
 64 
 65 /*
 66  * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY
 67  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 68  * @modules java.base/java.lang:+open
 69  * @library /test/lib
 70  * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 71  */
 72 
 73 /*
 74  * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT
 75  * @modules java.base/java.lang:+open
 76  * @library /test/lib
 77  * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 78  */
 79 
 80 /*
 81  * @test id=Xcomp-noTieredCompilation-LM_LEGACY
 82  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 83  * @modules java.base/java.lang:+open
 84  * @library /test/lib
 85  * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 86  */
 87 
 88 /*
 89  * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT
 90  * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64"
 91  * @modules java.base/java.lang:+open
 92  * @library /test/lib
 93  * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
 94  */
 95 
 96 import java.time.Duration;
 97 import java.util.ArrayList;
 98 import java.util.List;
 99 import java.util.concurrent.CountDownLatch;
100 import java.util.concurrent.ThreadFactory;
101 import java.util.concurrent.ThreadLocalRandom;
102 import java.util.concurrent.Executors;
103 import java.util.concurrent.ExecutorService;
104 import java.util.concurrent.atomic.AtomicBoolean;
105 import java.util.concurrent.locks.LockSupport;
106 import java.util.stream.IntStream;
107 import java.util.stream.Stream;
108 
109 import jdk.test.lib.thread.VThreadPinner;
110 import jdk.test.lib.thread.VThreadRunner;
111 import jdk.test.lib.thread.VThreadScheduler;
112 import org.junit.jupiter.api.Test;
113 import org.junit.jupiter.api.BeforeAll;
114 import org.junit.jupiter.api.RepeatedTest;
115 import org.junit.jupiter.params.ParameterizedTest;
116 import org.junit.jupiter.params.provider.Arguments;
117 import org.junit.jupiter.params.provider.ValueSource;
118 import org.junit.jupiter.params.provider.MethodSource;
119 import org.junit.jupiter.api.condition.*;
120 import static org.junit.jupiter.api.Assertions.*;
121 import static org.junit.jupiter.api.Assumptions.*;
122 
123 class MonitorEnterExit {
124     static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
125     static final int MAX_ENTER_DEPTH = 256;
126 
127     @BeforeAll
128     static void setup() {
129         // need >=2 carriers for testing pinning when main thread is a virtual thread
130         if (Thread.currentThread().isVirtual()) {
131             VThreadRunner.ensureParallelism(2);
132         }
133     }
134 
135     /**
136      * Test monitor enter with no contention.
137      */
138     @Test
139     void testEnterNoContention() throws Exception {
140         var lock = new Object();
141         VThreadRunner.run(() -> {
142             synchronized (lock) {
143                 assertTrue(Thread.holdsLock(lock));
144             }
145             assertFalse(Thread.holdsLock(lock));
146         });
147     }
148 
149     /**
150      * Test monitor enter with contention, monitor is held by platform thread.
151      */
152     @Test
153     void testEnterWhenHeldByPlatformThread() throws Exception {
154         testEnterWithContention();
155     }
156 
157     /**
158      * Test monitor enter with contention, monitor is held by virtual thread.
159      */
160     @Test
161     void testEnterWhenHeldByVirtualThread() throws Exception {
162         VThreadRunner.run(this::testEnterWithContention);
163     }
164 
165     /**
166      * Test monitor enter with contention, monitor will be held by caller thread.
167      */
168     private void testEnterWithContention() throws Exception {
169         var lock = new Object();
170         var started = new CountDownLatch(1);
171         var entered = new AtomicBoolean();
172         var vthread = Thread.ofVirtual().unstarted(() -> {
173             started.countDown();
174             synchronized (lock) {
175                 assertTrue(Thread.holdsLock(lock));
176                 entered.set(true);
177             }
178             assertFalse(Thread.holdsLock(lock));
179         });
180         try {
181             synchronized (lock) {
182                 vthread.start();
183 
184                 // wait for thread to start and block
185                 started.await();
186                 await(vthread, Thread.State.BLOCKED);
187 
188                 assertFalse(entered.get());
189             }
190         } finally {
191             vthread.join();
192         }
193         assertTrue(entered.get());
194     }
195 
196     /**
197      * Test monitor reenter.
198      */
199     @Test
200     void testReenter() throws Exception {
201         var lock = new Object();
202         VThreadRunner.run(() -> {
203             testReenter(lock, 0);
204             assertFalse(Thread.holdsLock(lock));
205         });
206     }
207 
208     private void testReenter(Object lock, int depth) {
209         if (depth < MAX_ENTER_DEPTH) {
210             synchronized (lock) {
211                 assertTrue(Thread.holdsLock(lock));
212                 testReenter(lock, depth + 1);
213                 assertTrue(Thread.holdsLock(lock));
214             }
215         }
216     }
217 
218     /**
219      * Test monitor reenter when there are other threads blocked trying to enter.
220      */
221     @Test
222     void testReenterWithContention() throws Exception {
223         var lock = new Object();
224         VThreadRunner.run(() -> {
225             List<Thread> threads = new ArrayList<>();
226             testReenter(lock, 0, threads);
227 
228             // wait for threads to terminate
229             for (Thread vthread : threads) {
230                 vthread.join();
231             }
232         });
233     }
234 
235     private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
236         if (depth < MAX_ENTER_DEPTH) {
237             synchronized (lock) {
238                 assertTrue(Thread.holdsLock(lock));
239 
240                 // start platform or virtual thread that blocks waiting to enter
241                 var started = new CountDownLatch(1);
242                 ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
243                         ? Thread.ofPlatform().factory()
244                         : Thread.ofVirtual().factory();
245                 var thread = factory.newThread(() -> {
246                     started.countDown();
247                     synchronized (lock) {
248                         /* do nothing */
249                     }
250                 });
251                 thread.start();
252 
253                 // wait for thread to start and block
254                 started.await();
255                 await(thread, Thread.State.BLOCKED);
256                 threads.add(thread);
257 
258                 // test reenter
259                 testReenter(lock, depth + 1, threads);
260             }
261         }
262     }
263 
264     /**
265      * Test monitor enter when pinned.
266      */
267     @Test
268     void testEnterWhenPinned() throws Exception {
269         var lock = new Object();
270         VThreadPinner.runPinned(() -> {
271             synchronized (lock) {
272                 assertTrue(Thread.holdsLock(lock));
273             }
274             assertFalse(Thread.holdsLock(lock));
275         });
276     }
277 
278     /**
279      * Test monitor reenter when pinned.
280      */
281     @Test
282     void testReenterWhenPinned() throws Exception {
283         VThreadRunner.run(() -> {
284             var lock = new Object();
285             synchronized (lock) {
286                 VThreadPinner.runPinned(() -> {
287                     assertTrue(Thread.holdsLock(lock));
288                     synchronized (lock) {
289                         assertTrue(Thread.holdsLock(lock));
290                     }
291                     assertTrue(Thread.holdsLock(lock));
292                 });
293             }
294             assertFalse(Thread.holdsLock(lock));
295         });
296     }
297 
298     /**
299      * Test contended monitor enter when pinned. Monitor is held by platform thread.
300      */
301     @Test
302     void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
303         testEnterWithContentionWhenPinned();
304     }
305 
306     /**
307      * Test contended monitor enter when pinned. Monitor is held by virtual thread.
308      */
309     @Test
310     void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
311         // need at least two carrier threads
312         int previousParallelism = VThreadRunner.ensureParallelism(2);
313         try {
314             VThreadRunner.run(this::testEnterWithContentionWhenPinned);
315         } finally {
316             VThreadRunner.setParallelism(previousParallelism);
317         }
318     }
319 
320     /**
321      * Test contended monitor enter when pinned, monitor will be held by caller thread.
322      */
323     private void testEnterWithContentionWhenPinned() throws Exception {
324         var lock = new Object();
325         var started = new CountDownLatch(1);
326         var entered = new AtomicBoolean();
327         Thread vthread  = Thread.ofVirtual().unstarted(() -> {
328             VThreadPinner.runPinned(() -> {
329                 started.countDown();
330                 synchronized (lock) {
331                     entered.set(true);
332                 }
333             });
334         });
335         synchronized (lock) {
336             // start thread and wait for it to block
337             vthread.start();
338             started.await();
339             await(vthread, Thread.State.BLOCKED);
340             assertFalse(entered.get());
341         }
342         vthread.join();
343 
344         // check thread entered monitor
345         assertTrue(entered.get());
346     }
347 
348     /**
349      * Test that parking while holding a monitor releases the carrier.
350      */
351     @ParameterizedTest
352     @ValueSource(booleans = { true, false })
353     void testReleaseWhenParked(boolean reenter) throws Exception {
354         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
355         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
356             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
357 
358             var lock = new Object();
359 
360             // thread enters (and maybe reenters) a monitor and parks
361             var started = new CountDownLatch(1);
362             var vthread1 = factory.newThread(() -> {
363                 started.countDown();
364                 synchronized (lock) {
365                     if (reenter) {
366                         synchronized (lock) {
367                             LockSupport.park();
368                         }
369                     } else {
370                         LockSupport.park();
371                     }
372                 }
373             });
374 
375             vthread1.start();
376             try {
377                 // wait for thread to start and park
378                 started.await();
379                 await(vthread1, Thread.State.WAITING);
380 
381                 // carrier should be released, use it for another thread
382                 var executed = new AtomicBoolean();
383                 var vthread2 = factory.newThread(() -> {
384                     executed.set(true);
385                 });
386                 vthread2.start();
387                 vthread2.join();
388                 assertTrue(executed.get());
389             } finally {
390                 LockSupport.unpark(vthread1);
391                 vthread1.join();
392             }
393         }
394     }
395 
396     /**
397      * Test that blocking waiting to enter a monitor releases the carrier.
398      */
399     @Test
400     void testReleaseWhenBlocked() throws Exception {
401         assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
402         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
403             ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
404 
405             var lock = new Object();
406 
407             // thread enters monitor
408             var started = new CountDownLatch(1);
409             var vthread1 = factory.newThread(() -> {
410                 started.countDown();
411                 synchronized (lock) {
412                 }
413             });
414 
415             try {
416                 synchronized (lock) {
417                     // start thread and wait for it to block
418                     vthread1.start();
419                     started.await();
420                     await(vthread1, Thread.State.BLOCKED);
421 
422                     // carrier should be released, use it for another thread
423                     var executed = new AtomicBoolean();
424                     var vthread2 = factory.newThread(() -> {
425                         executed.set(true);
426                     });
427                     vthread2.start();
428                     vthread2.join();
429                     assertTrue(executed.get());
430                 }
431             } finally {
432                 vthread1.join();
433             }
434         }
435     }
436 
437     /**
438      * Test lots of virtual threads parked while holding a monitor. If the number of
439      * virtual threads exceeds the number of carrier threads then this test will hang if
440      * parking doesn't release the carrier.
441      */
442     @Test
443     void testManyParkedThreads() throws Exception {
444         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
445         var done = new AtomicBoolean();
446         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
447             var lock = new Object();
448             var started = new CountDownLatch(1);
449             var vthread = Thread.ofVirtual().start(() -> {
450                 started.countDown();
451                 synchronized (lock) {
452                     while (!done.get()) {
453                         LockSupport.park();
454                     }
455                 }
456             });
457             // wait for thread to start and park
458             started.await();
459             await(vthread, Thread.State.WAITING);
460             vthreads[i] = vthread;
461         }
462 
463         // cleanup
464         done.set(true);
465         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
466             var vthread = vthreads[i];
467             LockSupport.unpark(vthread);
468             vthread.join();
469         }
470     }
471 
472     /**
473      * Test lots of virtual threads blocked waiting to enter a monitor. If the number
474      * of virtual threads exceeds the number of carrier threads this test will hang if
475      * carriers aren't released.
476      */
477     @Test
478     void testManyBlockedThreads() throws Exception {
479         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
480         var lock = new Object();
481         synchronized (lock) {
482             for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
483                 var started = new CountDownLatch(1);
484                 var vthread = Thread.ofVirtual().start(() -> {
485                     started.countDown();
486                     synchronized (lock) {
487                     }
488                 });
489                 // wait for thread to start and block
490                 started.await();
491                 await(vthread, Thread.State.BLOCKED);
492                 vthreads[i] = vthread;
493             }
494         }
495 
496         // cleanup
497         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
498             vthreads[i].join();
499         }
500     }
501 
502     /**
503      * Returns a stream of elements that are ordered pairs of platform and virtual thread
504      * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
505      */
506     static Stream<Arguments> threadCounts() {
507         return IntStream.range(0, 17)
508                 .filter(i -> i % 2 == 0)
509                 .mapToObj(i -> i)
510                 .flatMap(np -> IntStream.range(2, 33)
511                         .filter(i -> i % 2 == 0)
512                         .mapToObj(vp -> Arguments.of(np, vp)));
513     }
514 
515     /**
516      * Test mutual exclusion of monitors with platform and virtual threads.
517      */
518     @ParameterizedTest
519     @MethodSource("threadCounts")
520     void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
521         class Counter {
522             int count;
523             synchronized void increment() {
524                 count++;
525                 Thread.yield();
526             }
527         }
528         var counter = new Counter();
529         int nThreads = nPlatformThreads + nVirtualThreads;
530         var threads = new Thread[nThreads];
531         int index = 0;
532         for (int i = 0; i < nPlatformThreads; i++) {
533             threads[index] = Thread.ofPlatform()
534                     .name("platform-" + index)
535                     .unstarted(counter::increment);
536             index++;
537         }
538         for (int i = 0; i < nVirtualThreads; i++) {
539             threads[index] = Thread.ofVirtual()
540                     .name("virtual-" + index)
541                     .unstarted(counter::increment);
542             index++;
543         }
544         // start all threads
545         for (Thread thread : threads) {
546             thread.start();
547         }
548         // wait for all threads to terminate
549         for (Thread thread : threads) {
550             thread.join();
551         }
552         assertEquals(nThreads, counter.count);
553     }
554 
555     /**
556      * Test unblocking a virtual thread waiting to enter a monitor held by a platform thread.
557      */
558     @RepeatedTest(20)
559     void testUnblockingByPlatformThread() throws Exception {
560         testUnblocking();
561     }
562 
563     /**
564      * Test unblocking a virtual thread waiting to enter a monitor held by another
565      * virtual thread.
566      */
567     @RepeatedTest(20)
568     void testUnblockingByVirtualThread() throws Exception {
569         VThreadRunner.run(this::testUnblocking);
570     }
571 
572     /**
573      * Test unblocking a virtual thread waiting to enter a monitor, monitor will be
574      * initially be held by caller thread.
575      */
576     private void testUnblocking() throws Exception {
577         var lock = new Object();
578         var started = new CountDownLatch(1);
579         var entered = new AtomicBoolean();
580         var vthread = Thread.ofVirtual().unstarted(() -> {
581             started.countDown();
582             synchronized (lock) {
583                 entered.set(true);
584             }
585         });
586         try {
587             synchronized (lock) {
588                 vthread.start();
589                 started.await();
590 
591                 // random delay before exiting monitor
592                 switch (ThreadLocalRandom.current().nextInt(4)) {
593                     case 0 -> { /* no delay */}
594                     case 1 -> Thread.onSpinWait();
595                     case 2 -> Thread.yield();
596                     case 3 -> await(vthread, Thread.State.BLOCKED);
597                     default -> fail();
598                 }
599 
600                 assertFalse(entered.get());
601             }
602         } finally {
603             vthread.join();
604         }
605         assertTrue(entered.get());
606     }
607 
608     /**
609      * Test that unblocking a virtual thread waiting to enter a monitor does not consume
610      * the thread's parking permit.
611      */
612     @Test
613     void testParkingPermitNotConsumed() throws Exception {
614         var lock = new Object();
615         var started = new CountDownLatch(1);
616         var vthread = Thread.ofVirtual().unstarted(() -> {
617             started.countDown();
618             LockSupport.unpark(Thread.currentThread());
619             synchronized (lock) { }  // should block
620             LockSupport.park();      // should not park
621         });
622 
623         synchronized (lock) {
624             vthread.start();
625             // wait for thread to start and block
626             started.await();
627             await(vthread, Thread.State.BLOCKED);
628         }
629         vthread.join();
630     }
631 
632     /**
633      * Test that unblocking a virtual thread waiting to enter a monitor does not make
634      * available the thread's parking permit.
635      */
636     @Test
637     void testParkingPermitNotOffered() throws Exception {
638         var lock = new Object();
639         var started = new CountDownLatch(1);
640         var vthread = Thread.ofVirtual().unstarted(() -> {
641             started.countDown();
642             synchronized (lock) { }  // should block
643             LockSupport.park();      // should park
644         });
645 
646         synchronized (lock) {
647             vthread.start();
648             // wait for thread to start and block
649             started.await();
650             await(vthread, Thread.State.BLOCKED);
651         }
652 
653         try {
654             // wait for thread to park, it should not terminate
655             await(vthread, Thread.State.WAITING);
656             vthread.join(Duration.ofMillis(100));
657             assertEquals(Thread.State.WAITING, vthread.getState());
658         } finally {
659             LockSupport.unpark(vthread);
660             vthread.join();
661         }
662     }
663 
664     /**
665      * Waits for the given thread to reach a given state.
666      */
667     private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
668         Thread.State state = thread.getState();
669         while (state != expectedState) {
670             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
671             Thread.sleep(10);
672             state = thread.getState();
673         }
674     }
675 }