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