1 /*
  2  * Copyright (c) 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 Tests for object monitors that have been useful to find bugs
 27  * @library /test/lib
 28  * @requires vm.continuations & vm.opt.LockingMode != 1
 29  * @modules java.base/java.lang:+open
 30  * @run junit/othervm MiscMonitorTests
 31  */
 32 
 33 /*
 34  * @test id=Xint
 35  * @library /test/lib
 36  * @requires vm.continuations & vm.opt.LockingMode != 1
 37  * @modules java.base/java.lang:+open
 38  * @run junit/othervm -Xint MiscMonitorTests
 39  */
 40 
 41 /*
 42  * @test id=Xcomp
 43  * @library /test/lib
 44  * @requires vm.continuations & vm.opt.LockingMode != 1
 45  * @modules java.base/java.lang:+open
 46  * @run junit/othervm -Xcomp MiscMonitorTests
 47  */
 48 
 49 /*
 50  * @test id=Xcomp-TieredStopAtLevel3
 51  * @library /test/lib
 52  * @requires vm.continuations & vm.opt.LockingMode != 1
 53  * @modules java.base/java.lang:+open
 54  * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=3 MiscMonitorTests
 55  */
 56 
 57 /*
 58  * @test id=Xcomp-noTieredCompilation
 59  * @summary Test virtual threads using synchronized
 60  * @library /test/lib
 61  * @requires vm.continuations & vm.opt.LockingMode != 1
 62  * @modules java.base/java.lang:+open
 63  * @run junit/othervm -Xcomp -XX:-TieredCompilation MiscMonitorTests
 64  */
 65 
 66 /*
 67  * @test id=gc
 68  * @requires vm.debug == true & vm.continuations & vm.opt.LockingMode != 1
 69  * @library /test/lib
 70  * @modules java.base/java.lang:+open
 71  * @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 MiscMonitorTests
 72  */
 73 
 74 import java.util.concurrent.atomic.AtomicInteger;
 75 import java.util.concurrent.*;
 76 import java.util.ArrayList;
 77 import java.util.List;
 78 
 79 import jdk.test.lib.thread.VThreadScheduler;
 80 import org.junit.jupiter.api.Test;
 81 import static org.junit.jupiter.api.Assertions.*;
 82 
 83 class MiscMonitorTests {
 84     static final int CARRIER_COUNT = Runtime.getRuntime().availableProcessors();
 85 
 86     /**
 87      * Test that yielding while holding monitors releases carrier.
 88      */
 89     @Test
 90     void testReleaseOnYield() throws Exception {
 91         try (var test = new TestReleaseOnYield()) {
 92             test.runTest();
 93         }
 94     }
 95 
 96     private static class TestReleaseOnYield extends TestBase {
 97         final Object lock = new Object();
 98         volatile boolean finish;
 99         volatile int counter;
100 
101         @Override
102         void runTest() throws Exception {
103             int vthreadCount = CARRIER_COUNT;
104 
105             startVThreads(() -> foo(), vthreadCount, "Batch1");
106             sleep(500);  // Give time for threads to reach Thread.yield
107             startVThreads(() -> bar(), vthreadCount, "Batch2");
108 
109             while (counter != vthreadCount) {
110                 Thread.onSpinWait();
111             }
112             finish = true;
113             joinVThreads();
114         }
115 
116         void foo() {
117             Object lock = new Object();
118             synchronized (lock) {
119                 while (!finish) {
120                     Thread.yield();
121                 }
122             }
123             System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
124         }
125 
126         void bar() {
127             synchronized (lock) {
128                 counter++;
129             }
130             System.err.println("Exiting bar from thread " + Thread.currentThread().getName());
131         }
132     }
133 
134     /**
135      * Test yielding while holding monitors with recursive locking releases carrier.
136      */
137     @Test
138     void testReleaseOnYieldRecursive() throws Exception {
139         try (var test = new TestReleaseOnYieldRecursive()) {
140             test.runTest();
141         }
142     }
143 
144     private static class TestReleaseOnYieldRecursive extends TestBase {
145         final Object lock = new Object();
146         volatile boolean finish;
147         volatile int counter;
148 
149         @Override
150         void runTest() throws Exception {
151             int vthreadCount = CARRIER_COUNT;
152 
153             startVThreads(() -> foo(), vthreadCount, "Batch1");
154             sleep(500);  // Give time for threads to reach Thread.yield
155             startVThreads(() -> bar(), vthreadCount, "Batch2");
156 
157             while (counter != 2 * vthreadCount) {
158                 Thread.onSpinWait();
159             }
160             finish = true;
161             joinVThreads();
162         }
163 
164         void foo() {
165            Object lock = new Object();
166             synchronized (lock) {
167                 while (!finish) {
168                     Thread.yield();
169                 }
170             }
171             System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
172         }
173 
174         void bar() {
175             synchronized (lock) {
176                 counter++;
177             }
178             recursive(10);
179             System.err.println("Exiting bar from thread " + Thread.currentThread().getName());
180         };
181 
182         void recursive(int count) {
183             synchronized (Thread.currentThread()) {
184                 if (count > 0) {
185                     recursive(count - 1);
186                 } else {
187                     synchronized (lock) {
188                         counter++;
189                         Thread.yield();
190                     }
191                 }
192             }
193         }
194     }
195 
196     /**
197      * Test that contention on monitorenter releases carrier.
198      */
199     @Test
200     void testReleaseOnContention() throws Exception {
201         try (var test = new TestReleaseOnContention()) {
202             test.runTest();
203         }
204     }
205 
206     private static class TestReleaseOnContention extends TestBase {
207         final Object lock = new Object();
208         volatile boolean finish;
209         volatile int counter;
210 
211         @Override
212         void runTest() throws Exception {
213             int vthreadCount = CARRIER_COUNT * 8;
214 
215             startVThreads(() -> foo(), vthreadCount, "VThread");
216             sleep(500);  // Give time for threads to reach synchronized (lock)
217 
218             finish = true;
219             joinVThreads();
220         }
221 
222         void foo() {
223             synchronized (lock) {
224                 while (!finish) {
225                     Thread.yield();
226                 }
227             }
228             System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
229         }
230     }
231 
232     /**
233      * Test contention on monitorenter with extra monitors on stack shared by all threads.
234      */
235     @Test
236     void testContentionMultipleMonitors() throws Exception {
237         try (var test = new TestContentionMultipleMonitors()) {
238             test.runTest();
239         }
240     }
241 
242     private static class TestContentionMultipleMonitors extends TestBase {
243         static int MONITOR_COUNT = 12;
244         final Object[] lockArray = new Object[MONITOR_COUNT];
245         final AtomicInteger workerCount = new AtomicInteger(0);
246         volatile boolean finish;
247 
248         @Override
249         void runTest() throws Exception {
250             int vthreadCount = CARRIER_COUNT * 8;
251             for (int i = 0; i < MONITOR_COUNT; i++) {
252                 lockArray[i] = new Object();
253             }
254 
255             startVThreads(() -> foo(), vthreadCount, "VThread");
256 
257             sleep(5000);
258             finish = true;
259             joinVThreads();
260             assertEquals(vthreadCount, workerCount.get());
261         }
262 
263         void foo() {
264             while (!finish) {
265                 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
266                 synchronized (lockArray[lockNumber]) {
267                     recursive1(lockNumber, lockNumber);
268                 }
269             }
270             workerCount.getAndIncrement();
271             System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
272         }
273 
274         public void recursive1(int depth, int lockNumber) {
275             if (depth > 0) {
276                 recursive1(depth - 1, lockNumber);
277             } else {
278                 if (Math.random() < 0.5) {
279                     Thread.yield();
280                 }
281                 recursive2(lockNumber);
282             }
283         }
284 
285         public void recursive2(int lockNumber) {
286             if (lockNumber + 2 <= MONITOR_COUNT - 1) {
287                 lockNumber += 2;
288                 synchronized (lockArray[lockNumber]) {
289                     Thread.yield();
290                     recursive2(lockNumber);
291                 }
292             }
293         }
294     }
295 
296     /**
297      * Test contention on monitorenter with extra monitors on stack both local only and shared by all threads.
298      */
299     @Test
300     void testContentionMultipleMonitors2() throws Exception {
301         try (var test = new TestContentionMultipleMonitors2()) {
302             test.runTest();
303         }
304     }
305 
306     private static class TestContentionMultipleMonitors2 extends TestBase {
307         int MONITOR_COUNT = 12;
308         final Object[] lockArray = new Object[MONITOR_COUNT];
309         final AtomicInteger workerCount = new AtomicInteger(0);
310         volatile boolean finish;
311 
312         @Override
313         void runTest() throws Exception {
314             int vthreadCount = CARRIER_COUNT * 8;
315             for (int i = 0; i < MONITOR_COUNT; i++) {
316                 lockArray[i] = new Object();
317             }
318 
319             startVThreads(() -> foo(), vthreadCount, "VThread");
320 
321             sleep(5000);
322             finish = true;
323             joinVThreads();
324             assertEquals(vthreadCount, workerCount.get());
325         }
326 
327         void foo() {
328             Object[] myLockArray = new Object[MONITOR_COUNT];
329             for (int i = 0; i < MONITOR_COUNT; i++) {
330                 myLockArray[i] = new Object();
331             }
332 
333             while (!finish) {
334                 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
335                 synchronized (myLockArray[lockNumber]) {
336                     synchronized (lockArray[lockNumber]) {
337                         recursive1(lockNumber, lockNumber, myLockArray);
338                     }
339                 }
340             }
341             workerCount.getAndIncrement();
342             System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
343         }
344 
345         public void recursive1(int depth, int lockNumber, Object[] myLockArray) {
346             if (depth > 0) {
347                 recursive1(depth - 1, lockNumber, myLockArray);
348             } else {
349                 if (Math.random() < 0.5) {
350                     Thread.yield();
351                 }
352                 recursive2(lockNumber, myLockArray);
353             }
354         }
355 
356         public void recursive2(int lockNumber, Object[] myLockArray) {
357             if (lockNumber + 2 <= MONITOR_COUNT - 1) {
358                 lockNumber += 2;
359                 synchronized (myLockArray[lockNumber]) {
360                     if (Math.random() < 0.5) {
361                         Thread.yield();
362                     }
363                     synchronized (lockArray[lockNumber]) {
364                         Thread.yield();
365                         recursive2(lockNumber, myLockArray);
366                     }
367                 }
368             }
369         }
370     }
371 
372 
373     /**
374      * Test contention on monitorenter with synchronized methods.
375      */
376     @Test
377     void testContentionWithSyncMethods() throws Exception {
378         try (var test = new TestContentionWithSyncMethods()) {
379             test.runTest();
380         }
381     }
382 
383     private static class TestContentionWithSyncMethods extends TestBase {
384         static final int MONITOR_COUNT = 12;
385         final Object[] lockArray = new Object[MONITOR_COUNT];
386         final AtomicInteger workerCount = new AtomicInteger(0);
387         volatile boolean finish;
388 
389         @Override
390         void runTest() throws Exception {
391             int vthreadCount = CARRIER_COUNT * 8;
392             for (int i = 0; i < MONITOR_COUNT; i++) {
393                 lockArray[i] = new Object();
394             }
395 
396             startVThreads(() -> foo(), vthreadCount, "VThread");
397 
398             sleep(5000);
399             finish = true;
400             joinVThreads();
401             assertEquals(vthreadCount, workerCount.get());
402         }
403 
404         void foo() {
405             Object myLock = new Object();
406 
407             while (!finish) {
408                 int lockNumber = ThreadLocalRandom.current().nextInt(0, MONITOR_COUNT - 1);
409                 synchronized (myLock) {
410                     synchronized (lockArray[lockNumber]) {
411                         recursive(lockNumber, myLock);
412                     }
413                 }
414             }
415             workerCount.getAndIncrement();
416             System.err.println("Exiting foo from thread " + Thread.currentThread().getName());
417         };
418 
419         synchronized void recursive(int depth, Object myLock) {
420             if (depth > 0) {
421                 recursive(depth - 1, myLock);
422             } else {
423                 if (Math.random() < 0.5) {
424                     Thread.yield();
425                 } else {
426                     synchronized (myLock) {
427                         Thread.yield();
428                     }
429                 }
430             }
431         }
432     }
433 
434     /**
435      * Test wait/notify mechanism.
436      */
437     @Test
438     void waitNotifyTest() throws Exception {
439         int threadCount = 1000;
440         int waitTime = 50;
441         Thread[] vthread = new Thread[threadCount];
442         long start = System.currentTimeMillis();
443 
444         while (System.currentTimeMillis() - start < 5000) {
445             CountDownLatch latchStart = new CountDownLatch(threadCount);
446             CountDownLatch latchFinish = new CountDownLatch(threadCount);
447             Object object = new Object();
448 
449             for (int i = 0; i < threadCount; i++) {
450                 vthread[i] = Thread.ofVirtual().start(() -> {
451                     synchronized (object) {
452                         try {
453                             latchStart.countDown();
454                             object.wait(waitTime);
455                         } catch (InterruptedException e) {
456                             //do nothing;
457                         }
458                     }
459                     latchFinish.countDown();
460                 });
461             }
462 
463             try {
464                 latchStart.await();
465                 synchronized (object) {
466                     object.notifyAll();
467                 }
468                 latchFinish.await();
469                 for (int i = 0; i < threadCount; i++) {
470                     vthread[i].join();
471                 }
472             } catch (InterruptedException e) {
473                 //do nothing;
474             }
475         }
476     }
477 
478     private static abstract class TestBase implements AutoCloseable {
479         final ExecutorService scheduler = Executors.newFixedThreadPool(CARRIER_COUNT);
480         final List<Thread[]> vthreadList = new ArrayList<>();
481 
482         abstract void runTest() throws Exception;
483 
484         void startVThreads(Runnable r, int count, String name) {
485             Thread vthreads[] = new Thread[count];
486             for (int i = 0; i < count; i++) {
487                 vthreads[i] = VThreadScheduler.virtualThreadBuilder(scheduler).name(name + "-" + i).start(r);
488             }
489             vthreadList.add(vthreads);
490         }
491 
492         void joinVThreads() throws Exception {
493             for (Thread[] vthreads : vthreadList) {
494                 for (Thread vthread : vthreads) {
495                     vthread.join();
496                 }
497             }
498         }
499 
500         void sleep(long ms) throws Exception {
501             Thread.sleep(ms);
502         }
503 
504         @Override
505         public void close() {
506             scheduler.close();
507         }
508     }
509 }