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 entering (and owning) a lot of monitors 27 * @key randomness 28 * @library /test/lib 29 * @run junit LotsOfMonitors 30 */ 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.concurrent.CountDownLatch; 35 import java.util.concurrent.ThreadLocalRandom; 36 37 import jdk.test.lib.thread.VThreadRunner; 38 import org.junit.jupiter.api.Disabled; 39 import org.junit.jupiter.api.Test; 40 import static org.junit.jupiter.api.Assertions.*; 41 42 class LotsOfMonitors { 43 static final int MAX_MONITORS = 256; 44 45 /** 46 * Test entering lots of monitors with no contention. 47 */ 48 @Test 49 void testEnterNoContention() throws Exception { 50 VThreadRunner.run(() -> { 51 testEnter(List.of(), MAX_MONITORS); 52 }); 53 } 54 55 /** 56 * Enter the monitor for a new object, then reenter a monitor that is already held. 57 */ 58 private void testEnter(List<Object> locksHeld, int remaining) { 59 if (remaining > 0) { 60 var lock = new Object(); 61 assertFalse(Thread.holdsLock(lock)); 62 63 synchronized (lock) { 64 assertTrue(Thread.holdsLock(lock)); 65 66 var locks = new ArrayList<>(); 67 locks.addAll(locksHeld); 68 locks.add(lock); 69 testEnter(locks, remaining - 1); 70 assertTrue(Thread.holdsLock(lock)); 71 72 // reenter a lock that is already held 73 int index = ThreadLocalRandom.current().nextInt(locks.size()); 74 var otherLock = locks.get(index); 75 assertTrue(Thread.holdsLock(otherLock)); 76 synchronized (otherLock) { 77 assertTrue(Thread.holdsLock(otherLock)); 78 } 79 assertTrue(Thread.holdsLock(otherLock)); 80 81 assertTrue(Thread.holdsLock(lock)); 82 } 83 84 assertFalse(Thread.holdsLock(lock)); 85 } 86 } 87 88 /** 89 * Test entering lots of monitors with contention. 90 */ 91 @Disabled 92 @Test 93 void testEnterWithContention() throws Exception { 94 VThreadRunner.run(() -> { 95 var threads = new ArrayList<Thread>(); 96 testEnter(MAX_MONITORS, threads); 97 assertEquals(MAX_MONITORS, threads.size()); 98 99 // wait for threads to terminate 100 for (Thread vthread : threads) { 101 vthread.join(); 102 } 103 }); 104 } 105 106 /** 107 * Enter the monitor for a new object, racing with another thread that attempts to 108 * enter around the same time. 109 */ 110 private void testEnter(int remaining, List<Thread> threads) throws Exception { 111 if (remaining > 0) { 112 var lock = new Object(); 113 assertFalse(Thread.holdsLock(lock)); 114 115 // start thread to enter monitor for brief period, then enters again when signalled 116 var started = new CountDownLatch(1); 117 var signal = new CountDownLatch(1); 118 var thread = Thread.ofVirtual().start(() -> { 119 started.countDown(); 120 // enter, may be contended 121 synchronized (lock) { 122 Thread.onSpinWait(); 123 } 124 // wait to be signalled 125 try { 126 signal.await(); 127 } catch (InterruptedException e) { 128 } 129 // enter again 130 synchronized (lock) { 131 // do nothing 132 } 133 }); 134 started.await(); 135 136 // enter, may be contended 137 synchronized (lock) { 138 assertTrue(Thread.holdsLock(lock)); 139 140 // signal thread to enter monitor again, it should block 141 signal.countDown(); 142 await(thread, Thread.State.BLOCKED); 143 144 threads.add(thread); 145 146 testEnter(remaining -1, threads); 147 } 148 149 assertFalse(Thread.holdsLock(lock)); 150 } 151 } 152 153 private void await(Thread thread, Thread.State expectedState) { 154 Thread.State state = thread.getState(); 155 while (state != expectedState) { 156 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 157 Thread.yield(); 158 state = thread.getState(); 159 } 160 } 161 }