1 /*
  2  * Copyright (c) 2023, 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
 26  * @summary Test virtual thread monitor enter/exit
 27  * @key randomness
 28  * @modules java.base/java.lang:+open
 29  * @library /test/lib
 30  * @run junit/othervm --enable-native-access=ALL-UNNAMED Monitors
 31  */
 32 
 33 import java.time.Duration;
 34 import java.util.ArrayList;
 35 import java.util.List;
 36 import java.util.concurrent.CountDownLatch;
 37 import java.util.concurrent.ThreadFactory;
 38 import java.util.concurrent.ThreadLocalRandom;
 39 import java.util.concurrent.Executors;
 40 import java.util.concurrent.ExecutorService;
 41 import java.util.concurrent.atomic.AtomicBoolean;
 42 import java.util.concurrent.locks.LockSupport;
 43 
 44 import jdk.test.lib.thread.VThreadRunner;
 45 import jdk.test.lib.thread.VThreadPinner;
 46 import org.junit.jupiter.api.Disabled;
 47 import org.junit.jupiter.api.Test;
 48 import static org.junit.jupiter.api.Assertions.*;
 49 import static org.junit.jupiter.api.Assumptions.*;
 50 
 51 class Monitors {
 52     // change this to availableProcessors() * 4 when monitor pining issue resolved
 53     static final int MAX_VTHREAD_COUNT = Runtime.getRuntime().availableProcessors();
 54 
 55     // change this to 256 when monitor pining issue resolved
 56     static final int MAX_ENTER_DEPTH = MAX_VTHREAD_COUNT - 1;
 57 
 58     static ThreadFactory randomThreadFactory() {
 59         return ThreadLocalRandom.current().nextBoolean()
 60                 ? Thread.ofPlatform().factory()
 61                 : Thread.ofVirtual().factory();
 62     }
 63 
 64     /**
 65      * Test monitor enter with no contention.
 66      */
 67     @Test
 68     void testEnterNoContention() throws Exception {
 69         var lock = new Object();
 70         VThreadRunner.run(() -> {
 71             assertFalse(Thread.holdsLock(lock));
 72             synchronized (lock) {
 73                 assertTrue(Thread.holdsLock(lock));
 74             }
 75             assertFalse(Thread.holdsLock(lock));
 76         });
 77     }
 78 
 79     /**
 80      * Test monitor enter where monitor is held by platform thread.
 81      */
 82     @Test
 83     void testEnterWithContention() throws Exception {
 84         var lock = new Object();
 85         var started = new CountDownLatch(1);
 86         var entered = new AtomicBoolean();
 87         var vthread = Thread.ofVirtual().unstarted(() -> {
 88             started.countDown();
 89             synchronized (lock) {
 90                 entered.set(true);
 91             }
 92         });
 93         try {
 94             synchronized (lock) {
 95                 vthread.start();
 96 
 97                 // wait for thread to start and block
 98                 started.await();
 99                 await(vthread, Thread.State.BLOCKED);
100             }
101         } finally {
102             vthread.join();
103         }
104         assertTrue(entered.get());
105     }
106 
107     /**
108      * Test monitor enter where monitor is held by virtual thread.
109      */
110     @Disabled(value="Disabled due to pinning")
111     @Test
112     void testEnterWithContention2() throws Exception {
113         VThreadRunner.run(this::testEnterWithContention);
114     }
115 
116     /**
117      * Test monitor reenter.
118      */
119     @Test
120     void testReenter() throws Exception {
121         var lock = new Object();
122         VThreadRunner.run(() -> {
123             assertFalse(Thread.holdsLock(lock));
124             testReenter(lock, 0);
125             assertFalse(Thread.holdsLock(lock));
126         });
127     }
128 
129     private void testReenter(Object lock, int depth) {
130         if (depth < MAX_ENTER_DEPTH) {
131             synchronized (lock) {
132                 assertTrue(Thread.holdsLock(lock));
133                 testReenter(lock, depth + 1);
134                 assertTrue(Thread.holdsLock(lock));
135             }
136         }
137     }
138 
139     /**
140      * Test monitor reenter when there are threads blocked trying to enter the monitor.
141      */
142     @Test
143     void testReenterWithContention() throws Exception {
144         var lock = new Object();
145         VThreadRunner.run(() -> {
146             List<Thread> threads = new ArrayList<>();
147             testReenter(lock, 0, threads);
148 
149             // wait for threads to terminate
150             for (Thread vthread : threads) {
151                 vthread.join();
152             }
153         });
154     }
155 
156     private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
157         if (depth < MAX_ENTER_DEPTH) {
158             synchronized (lock) {
159                 assertTrue(Thread.holdsLock(lock));
160 
161                 // start thread that blocks waiting to enter
162                 var started = new CountDownLatch(1);
163                 var thread = randomThreadFactory().newThread(() -> {
164                     started.countDown();
165                     synchronized (lock) {
166                         /* do nothing */
167                     }
168                 });
169                 thread.start();
170 
171                 // wait for thread to start and block
172                 started.await();
173                 await(thread, Thread.State.BLOCKED);
174                 threads.add(thread);
175 
176                 // test reenter
177                 testReenter(lock, depth + 1, threads);
178             }
179         }
180     }
181 
182     /**
183      * Test monitor enter when pinned.
184      */
185     @Test
186     void testEnterWhenPinned() throws Exception {
187         VThreadRunner.run(() -> {
188             var lock = new Object();
189             VThreadPinner.runPinned(() -> {
190                 synchronized (lock) {
191                     assertTrue(Thread.holdsLock(lock));
192                 }
193             });
194             assertFalse(Thread.holdsLock(lock));
195         });
196     }
197 
198     /**
199      * Test monitor reenter when pinned.
200      */
201     @Test
202     void testReenterWhenPinned() throws Exception {
203         VThreadRunner.run(() -> {
204             var lock = new Object();
205             synchronized (lock) {
206                 VThreadPinner.runPinned(() -> {
207                     assertTrue(Thread.holdsLock(lock));
208                     synchronized (lock) {
209                         assertTrue(Thread.holdsLock(lock));
210                     }
211                     assertTrue(Thread.holdsLock(lock));
212                 });
213             }
214             assertFalse(Thread.holdsLock(lock));
215         });
216     }
217 
218     /**
219      * Test contended monitor enter when pinned. Monitor is held by platform thread.
220      */
221     @Test
222     void testContendedMonitorEnterWhenPinned() throws Exception {
223         var lock = new Object();
224         var started = new CountDownLatch(1);
225         var entered = new AtomicBoolean();
226         Thread vthread  = Thread.ofVirtual().unstarted(() -> {
227             VThreadPinner.runPinned(() -> {
228                 started.countDown();
229                 synchronized (lock) {
230                     entered.set(true);
231                 }
232             });
233         });
234         synchronized (lock) {
235             // start thread and wait for it to block
236             vthread.start();
237             started.await();
238             await(vthread, Thread.State.BLOCKED);
239         }
240         vthread.join();
241 
242         // check thread entered monitor
243         assertTrue(entered.get());
244     }
245 
246     /**
247      * Test contended monitor enter when pinned. Monitor is held by virtual thread.
248      */
249     @Disabled(value="Disabled due to pinning")
250     @Test
251     void testContendedMonitorEnterWhenPinned2() throws Exception {
252         VThreadRunner.run(this::testContendedMonitorEnterWhenPinned);
253     }
254 
255     /**
256      * Test that parking while holding a monitor releases the carrier.
257      */
258     @Disabled(value="Disabled due to pinning")
259     @Test
260     void testReleaseWhenParked() throws Exception {
261         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
262         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
263             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
264 
265             var lock = new Object();
266 
267             // thread enters monitor and parks
268             var started = new CountDownLatch(1);
269             var vthread1 = builder.start(() -> {
270                 started.countDown();
271                 synchronized (lock) {
272                     LockSupport.park();
273                 }
274             });
275 
276             try {
277                 // wait for thread to start and park
278                 started.await();
279                 await(vthread1, Thread.State.WAITING);
280 
281                 // carrier should be released, use it for another thread
282                 var executed = new AtomicBoolean();
283                 var vthread2 = builder.start(() -> {
284                     executed.set(true);
285                 });
286                 vthread2.join();
287                 assertTrue(executed.get());
288             } finally {
289                 LockSupport.unpark(vthread1);
290                 vthread1.join();
291             }
292         }
293     }
294 
295     /**
296      * Test that blocked waiting to enter a monitor releases the carrier.
297      */
298     @Disabled(value="Disabled due to pinning")
299     @Test
300     void testReleaseWhenBlocked() throws Exception {
301         assumeTrue(ThreadBuilders.supportsCustomScheduler(), "No support for custom schedulers");
302         try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
303             Thread.Builder builder = ThreadBuilders.virtualThreadBuilder(scheduler);
304 
305             var lock = new Object();
306 
307             // thread enters monitor
308             var started = new CountDownLatch(1);
309             var vthread1 = builder.unstarted(() -> {
310                 started.countDown();
311                 synchronized (lock) {
312                 }
313             });
314 
315             try {
316                 synchronized (lock) {
317                     // start thread and wait for it to block
318                     vthread1.start();
319                     started.await();
320                     await(vthread1, Thread.State.BLOCKED);
321 
322                     // carrier should be released, use it for another thread
323                     var executed = new AtomicBoolean();
324                     var vthread2 = builder.start(() -> {
325                         executed.set(true);
326                     });
327                     vthread2.join();
328                     assertTrue(executed.get());
329                 }
330             } finally {
331                 vthread1.join();
332             }
333         }
334     }
335 
336     /**
337      * Test lots of virtual threads parked while holding a monitor. If the number of
338      * virtual threads exceeds the number of carrier threads then this test will hang if
339      * carriers aren't released.
340      */
341     @Test
342     void testManyParkedThreads() throws Exception {
343         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
344         var done = new AtomicBoolean();
345         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
346             var lock = new Object();
347             var started = new CountDownLatch(1);
348             var vthread = Thread.ofVirtual().start(() -> {
349                 started.countDown();
350                 synchronized (lock) {
351                     while (!done.get()) {
352                         LockSupport.park();
353                     }
354                 }
355             });
356             // wait for thread to start and park
357             started.await();
358             await(vthread, Thread.State.WAITING);
359             vthreads[i] = vthread;
360         }
361 
362         // cleanup
363         done.set(true);
364         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
365             var vthread = vthreads[i];
366             LockSupport.unpark(vthread);
367             vthread.join();
368         }
369     }
370 
371     /**
372      * Test lots of virtual threads blocked waiting to enter a monitor. If the number
373      * of virtual threads exceeds the number of carrier threads this test will hang if
374      * carriers aren't released.
375      */
376     @Test
377     void testManyBlockedThreads() throws Exception {
378         Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
379         var lock = new Object();
380         synchronized (lock) {
381             for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
382                 var started = new CountDownLatch(1);
383                 var vthread = Thread.ofVirtual().start(() -> {
384                     started.countDown();
385                     synchronized (lock) {
386                     }
387                 });
388                 // wait for thread to start and block
389                 started.await();
390                 await(vthread, Thread.State.BLOCKED);
391                 vthreads[i] = vthread;
392             }
393         }
394 
395         // cleanup
396         for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
397             vthreads[i].join();
398         }
399     }
400 
401     /**
402      * Test that unblocking a virtual thread waiting to enter a monitor does not consume
403      * the thread's parking permit.
404      */
405     @Test
406     void testParkingPermitNotConsumed() throws Exception {
407         var lock = new Object();
408         var started = new CountDownLatch(1);
409         var vthread = Thread.ofVirtual().unstarted(() -> {
410             started.countDown();
411             LockSupport.unpark(Thread.currentThread());
412             synchronized (lock) { }  // should block
413             LockSupport.park();      // should not park
414         });
415 
416         synchronized (lock) {
417             vthread.start();
418             // wait for thread to start and block
419             started.await();
420             await(vthread, Thread.State.BLOCKED);
421         }
422         vthread.join();
423     }
424 
425     /**
426      * Test that unblocking a virtual thread waiting to enter a monitor does not make
427      * available the thread's parking permit.
428      */
429     @Test
430     void testParkingPermitNotOffered() throws Exception {
431         var lock = new Object();
432         var started = new CountDownLatch(1);
433         var vthread = Thread.ofVirtual().unstarted(() -> {
434             started.countDown();
435             synchronized (lock) { }  // should block
436             LockSupport.park();      // should park
437         });
438 
439         synchronized (lock) {
440             vthread.start();
441             // wait for thread to start and block
442             started.await();
443             await(vthread, Thread.State.BLOCKED);
444         }
445 
446         try {
447             // wait for thread to park, it should not terminate
448             await(vthread, Thread.State.WAITING);
449             vthread.join(Duration.ofMillis(100));
450             assertEquals(Thread.State.WAITING, vthread.getState());
451         } finally {
452             LockSupport.unpark(vthread);
453             vthread.join();
454         }
455     }
456 
457     /**
458      * Wait for the given thread to reach the given state.
459      */
460     private void await(Thread thread, Thread.State expectedState) {
461         Thread.State state = thread.getState();
462         while (state != expectedState) {
463             assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
464             Thread.yield();
465             state = thread.getState();
466         }
467     }
468 }