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 * @modules java.base/java.lang:+open jdk.management
28 * @library /test/lib
29 * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
30 */
31
32 import java.time.Duration;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.concurrent.CountDownLatch;
36 import java.util.concurrent.ThreadFactory;
37 import java.util.concurrent.ThreadLocalRandom;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.atomic.AtomicBoolean;
41 import java.util.concurrent.locks.LockSupport;
42 import java.util.stream.IntStream;
43 import java.util.stream.Stream;
44
45 import jdk.test.lib.thread.VThreadPinner;
46 import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
47 import jdk.test.lib.thread.VThreadScheduler;
48 import org.junit.jupiter.api.Test;
49 import org.junit.jupiter.api.BeforeAll;
50 import org.junit.jupiter.api.RepeatedTest;
51 import org.junit.jupiter.params.ParameterizedTest;
52 import org.junit.jupiter.params.provider.Arguments;
53 import org.junit.jupiter.params.provider.ValueSource;
54 import org.junit.jupiter.params.provider.MethodSource;
55 import org.junit.jupiter.api.condition.*;
56 import static org.junit.jupiter.api.Assertions.*;
57 import static org.junit.jupiter.api.Assumptions.*;
58
59 class MonitorEnterExit {
60 static final int MAX_ENTER_DEPTH = 256;
61
62 @BeforeAll
63 static void setup() {
64 // need >=2 carriers for testing pinning when main thread is a virtual thread
65 if (Thread.currentThread().isVirtual()) {
66 VThreadRunner.ensureParallelism(2);
67 }
68 }
69
70 /**
71 * Test monitor enter with no contention.
72 */
73 @Test
74 void testEnterNoContention() throws Exception {
75 var lock = new Object();
76 VThreadRunner.run(() -> {
77 synchronized (lock) {
78 assertTrue(Thread.holdsLock(lock));
79 }
80 assertFalse(Thread.holdsLock(lock));
81 });
82 }
83
84 /**
85 * Test monitor enter with contention, monitor is held by platform thread.
86 */
87 @Test
133 */
134 @Test
135 void testReenter() throws Exception {
136 var lock = new Object();
137 VThreadRunner.run(() -> {
138 testReenter(lock, 0);
139 assertFalse(Thread.holdsLock(lock));
140 });
141 }
142
143 private void testReenter(Object lock, int depth) {
144 if (depth < MAX_ENTER_DEPTH) {
145 synchronized (lock) {
146 assertTrue(Thread.holdsLock(lock));
147 testReenter(lock, depth + 1);
148 assertTrue(Thread.holdsLock(lock));
149 }
150 }
151 }
152
153 /**
154 * Test monitor enter when pinned.
155 */
156 @Test
157 void testEnterWhenPinned() throws Exception {
158 var lock = new Object();
159 VThreadPinner.runPinned(() -> {
160 synchronized (lock) {
161 assertTrue(Thread.holdsLock(lock));
162 }
163 assertFalse(Thread.holdsLock(lock));
164 });
165 }
166
167 /**
168 * Test monitor reenter when pinned.
169 */
170 @Test
171 void testReenterWhenPinned() throws Exception {
172 VThreadRunner.run(() -> {
180 assertTrue(Thread.holdsLock(lock));
181 });
182 }
183 assertFalse(Thread.holdsLock(lock));
184 });
185 }
186
187 /**
188 * Test contended monitor enter when pinned. Monitor is held by platform thread.
189 */
190 @Test
191 void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
192 testEnterWithContentionWhenPinned();
193 }
194
195 /**
196 * Test contended monitor enter when pinned. Monitor is held by virtual thread.
197 */
198 @Test
199 void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
200 // need at least two carrier threads
201 int previousParallelism = VThreadRunner.ensureParallelism(2);
202 try {
203 VThreadRunner.run(this::testEnterWithContentionWhenPinned);
204 } finally {
205 VThreadRunner.setParallelism(previousParallelism);
206 }
207 }
208
209 /**
210 * Test contended monitor enter when pinned, monitor will be held by caller thread.
211 */
212 private void testEnterWithContentionWhenPinned() throws Exception {
213 var lock = new Object();
214 var started = new CountDownLatch(1);
215 var entered = new AtomicBoolean();
216 Thread vthread = Thread.ofVirtual().unstarted(() -> {
217 VThreadPinner.runPinned(() -> {
218 started.countDown();
219 synchronized (lock) {
220 entered.set(true);
221 }
222 });
223 });
224 synchronized (lock) {
225 // start thread and wait for it to block
226 vthread.start();
227 started.await();
228 await(vthread, Thread.State.BLOCKED);
229 assertFalse(entered.get());
230 }
231 vthread.join();
232
233 // check thread entered monitor
234 assertTrue(entered.get());
235 }
236
237 /**
238 * Returns a stream of elements that are ordered pairs of platform and virtual thread
239 * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
240 */
241 static Stream<Arguments> threadCounts() {
242 return IntStream.range(0, 17)
243 .filter(i -> i % 2 == 0)
244 .mapToObj(i -> i)
245 .flatMap(np -> IntStream.range(2, 33)
246 .filter(i -> i % 2 == 0)
247 .mapToObj(vp -> Arguments.of(np, vp)));
248 }
249
250 /**
251 * Test mutual exclusion of monitors with platform and virtual threads.
252 */
253 @ParameterizedTest
254 @MethodSource("threadCounts")
255 void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
256 class Counter {
|
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 * @modules java.base/java.lang:+open jdk.management
28 * @library /test/lib
29 * @build LockingMode
30 * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit
31 */
32
33 /*
34 * @test id=LM_LEGACY
35 * @modules java.base/java.lang:+open jdk.management
36 * @library /test/lib
37 * @build LockingMode
38 * @run junit/othervm -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
39 */
40
41 /*
42 * @test id=LM_LIGHTWEIGHT
43 * @modules java.base/java.lang:+open jdk.management
44 * @library /test/lib
45 * @build LockingMode
46 * @run junit/othervm -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
47 */
48
49 /*
50 * @test id=Xint-LM_LEGACY
51 * @modules java.base/java.lang:+open jdk.management
52 * @library /test/lib
53 * @build LockingMode
54 * @run junit/othervm -Xint -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
55 */
56
57 /*
58 * @test id=Xint-LM_LIGHTWEIGHT
59 * @modules java.base/java.lang:+open jdk.management
60 * @library /test/lib
61 * @build LockingMode
62 * @run junit/othervm -Xint -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
63 */
64
65 /*
66 * @test id=Xcomp-LM_LEGACY
67 * @modules java.base/java.lang:+open jdk.management
68 * @library /test/lib
69 * @build LockingMode
70 * @run junit/othervm -Xcomp -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
71 */
72
73 /*
74 * @test id=Xcomp-LM_LIGHTWEIGHT
75 * @modules java.base/java.lang:+open jdk.management
76 * @library /test/lib
77 * @build LockingMode
78 * @run junit/othervm -Xcomp -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
79 */
80
81 /*
82 * @test id=Xcomp-TieredStopAtLevel1-LM_LEGACY
83 * @modules java.base/java.lang:+open jdk.management
84 * @library /test/lib
85 * @build LockingMode
86 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
87 */
88
89 /*
90 * @test id=Xcomp-TieredStopAtLevel1-LM_LIGHTWEIGHT
91 * @modules java.base/java.lang:+open jdk.management
92 * @library /test/lib
93 * @build LockingMode
94 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
95 */
96
97 /*
98 * @test id=Xcomp-noTieredCompilation-LM_LEGACY
99 * @modules java.base/java.lang:+open jdk.management
100 * @library /test/lib
101 * @build LockingMode
102 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=1 --enable-native-access=ALL-UNNAMED MonitorEnterExit
103 */
104
105 /*
106 * @test id=Xcomp-noTieredCompilation-LM_LIGHTWEIGHT
107 * @modules java.base/java.lang:+open jdk.management
108 * @library /test/lib
109 * @build LockingMode
110 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:LockingMode=2 --enable-native-access=ALL-UNNAMED MonitorEnterExit
111 */
112
113 import java.time.Duration;
114 import java.util.ArrayList;
115 import java.util.List;
116 import java.util.concurrent.CountDownLatch;
117 import java.util.concurrent.ThreadFactory;
118 import java.util.concurrent.ThreadLocalRandom;
119 import java.util.concurrent.Executors;
120 import java.util.concurrent.ExecutorService;
121 import java.util.concurrent.atomic.AtomicBoolean;
122 import java.util.concurrent.locks.LockSupport;
123 import java.util.stream.IntStream;
124 import java.util.stream.Stream;
125
126 import jdk.test.lib.thread.VThreadPinner;
127 import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management
128 import jdk.test.lib.thread.VThreadScheduler;
129 import org.junit.jupiter.api.Test;
130 import org.junit.jupiter.api.BeforeAll;
131 import org.junit.jupiter.api.RepeatedTest;
132 import org.junit.jupiter.api.condition.DisabledIf;
133 import org.junit.jupiter.params.ParameterizedTest;
134 import org.junit.jupiter.params.provider.Arguments;
135 import org.junit.jupiter.params.provider.ValueSource;
136 import org.junit.jupiter.params.provider.MethodSource;
137 import static org.junit.jupiter.api.Assertions.*;
138 import static org.junit.jupiter.api.Assumptions.*;
139
140 class MonitorEnterExit {
141 static final int MAX_VTHREAD_COUNT = 4 * Runtime.getRuntime().availableProcessors();
142 static final int MAX_ENTER_DEPTH = 256;
143
144 @BeforeAll
145 static void setup() {
146 // need >=2 carriers for tests that pin
147 VThreadRunner.ensureParallelism(2);
148 }
149
150 /**
151 * Test monitor enter with no contention.
152 */
153 @Test
154 void testEnterNoContention() throws Exception {
155 var lock = new Object();
156 VThreadRunner.run(() -> {
157 synchronized (lock) {
158 assertTrue(Thread.holdsLock(lock));
159 }
160 assertFalse(Thread.holdsLock(lock));
161 });
162 }
163
164 /**
165 * Test monitor enter with contention, monitor is held by platform thread.
166 */
167 @Test
213 */
214 @Test
215 void testReenter() throws Exception {
216 var lock = new Object();
217 VThreadRunner.run(() -> {
218 testReenter(lock, 0);
219 assertFalse(Thread.holdsLock(lock));
220 });
221 }
222
223 private void testReenter(Object lock, int depth) {
224 if (depth < MAX_ENTER_DEPTH) {
225 synchronized (lock) {
226 assertTrue(Thread.holdsLock(lock));
227 testReenter(lock, depth + 1);
228 assertTrue(Thread.holdsLock(lock));
229 }
230 }
231 }
232
233 /**
234 * Test monitor reenter when there are other threads blocked trying to enter.
235 */
236 @Test
237 @DisabledIf("LockingMode#isLegacy")
238 void testReenterWithContention() throws Exception {
239 var lock = new Object();
240 VThreadRunner.run(() -> {
241 List<Thread> threads = new ArrayList<>();
242 testReenter(lock, 0, threads);
243
244 // wait for threads to terminate
245 for (Thread vthread : threads) {
246 vthread.join();
247 }
248 });
249 }
250
251 private void testReenter(Object lock, int depth, List<Thread> threads) throws Exception {
252 if (depth < MAX_ENTER_DEPTH) {
253 synchronized (lock) {
254 assertTrue(Thread.holdsLock(lock));
255
256 // start platform or virtual thread that blocks waiting to enter
257 var started = new CountDownLatch(1);
258 ThreadFactory factory = ThreadLocalRandom.current().nextBoolean()
259 ? Thread.ofPlatform().factory()
260 : Thread.ofVirtual().factory();
261 var thread = factory.newThread(() -> {
262 started.countDown();
263 synchronized (lock) {
264 /* do nothing */
265 }
266 });
267 thread.start();
268
269 // wait for thread to start and block
270 started.await();
271 await(thread, Thread.State.BLOCKED);
272 threads.add(thread);
273
274 // test reenter
275 testReenter(lock, depth + 1, threads);
276 }
277 }
278 }
279
280 /**
281 * Test monitor enter when pinned.
282 */
283 @Test
284 void testEnterWhenPinned() throws Exception {
285 var lock = new Object();
286 VThreadPinner.runPinned(() -> {
287 synchronized (lock) {
288 assertTrue(Thread.holdsLock(lock));
289 }
290 assertFalse(Thread.holdsLock(lock));
291 });
292 }
293
294 /**
295 * Test monitor reenter when pinned.
296 */
297 @Test
298 void testReenterWhenPinned() throws Exception {
299 VThreadRunner.run(() -> {
307 assertTrue(Thread.holdsLock(lock));
308 });
309 }
310 assertFalse(Thread.holdsLock(lock));
311 });
312 }
313
314 /**
315 * Test contended monitor enter when pinned. Monitor is held by platform thread.
316 */
317 @Test
318 void testContendedEnterWhenPinnedHeldByPlatformThread() throws Exception {
319 testEnterWithContentionWhenPinned();
320 }
321
322 /**
323 * Test contended monitor enter when pinned. Monitor is held by virtual thread.
324 */
325 @Test
326 void testContendedEnterWhenPinnedHeldByVirtualThread() throws Exception {
327 VThreadRunner.run(this::testEnterWithContentionWhenPinned);
328 }
329
330 /**
331 * Test contended monitor enter when pinned, monitor will be held by caller thread.
332 */
333 private void testEnterWithContentionWhenPinned() throws Exception {
334 var lock = new Object();
335 var started = new CountDownLatch(1);
336 var entered = new AtomicBoolean();
337 Thread vthread = Thread.ofVirtual().unstarted(() -> {
338 VThreadPinner.runPinned(() -> {
339 started.countDown();
340 synchronized (lock) {
341 entered.set(true);
342 }
343 });
344 });
345 synchronized (lock) {
346 // start thread and wait for it to block
347 vthread.start();
348 started.await();
349 await(vthread, Thread.State.BLOCKED);
350 assertFalse(entered.get());
351 }
352 vthread.join();
353
354 // check thread entered monitor
355 assertTrue(entered.get());
356 }
357
358 /**
359 * Test that blocking waiting to enter a monitor releases the carrier.
360 */
361 @Test
362 @DisabledIf("LockingMode#isLegacy")
363 void testReleaseWhenBlocked() throws Exception {
364 assumeTrue(VThreadScheduler.supportsCustomScheduler(), "No support for custom schedulers");
365 try (ExecutorService scheduler = Executors.newFixedThreadPool(1)) {
366 ThreadFactory factory = VThreadScheduler.virtualThreadFactory(scheduler);
367
368 var lock = new Object();
369
370 // thread enters monitor
371 var started = new CountDownLatch(1);
372 var vthread1 = factory.newThread(() -> {
373 started.countDown();
374 synchronized (lock) {
375 }
376 });
377
378 try {
379 synchronized (lock) {
380 // start thread and wait for it to block
381 vthread1.start();
382 started.await();
383 await(vthread1, Thread.State.BLOCKED);
384
385 // carrier should be released, use it for another thread
386 var executed = new AtomicBoolean();
387 var vthread2 = factory.newThread(() -> {
388 executed.set(true);
389 });
390 vthread2.start();
391 vthread2.join();
392 assertTrue(executed.get());
393 }
394 } finally {
395 vthread1.join();
396 }
397 }
398 }
399
400 /**
401 * Test lots of virtual threads blocked waiting to enter a monitor. If the number
402 * of virtual threads exceeds the number of carrier threads this test will hang if
403 * carriers aren't released.
404 */
405 @Test
406 @DisabledIf("LockingMode#isLegacy")
407 void testManyBlockedThreads() throws Exception {
408 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
409 var lock = new Object();
410 synchronized (lock) {
411 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
412 var started = new CountDownLatch(1);
413 var vthread = Thread.ofVirtual().start(() -> {
414 started.countDown();
415 synchronized (lock) {
416 }
417 });
418 // wait for thread to start and block
419 started.await();
420 await(vthread, Thread.State.BLOCKED);
421 vthreads[i] = vthread;
422 }
423 }
424
425 // cleanup
426 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
427 vthreads[i].join();
428 }
429 }
430
431 /**
432 * Returns a stream of elements that are ordered pairs of platform and virtual thread
433 * counts. 0,2,4,..16 platform threads. 2,4,6,..32 virtual threads.
434 */
435 static Stream<Arguments> threadCounts() {
436 return IntStream.range(0, 17)
437 .filter(i -> i % 2 == 0)
438 .mapToObj(i -> i)
439 .flatMap(np -> IntStream.range(2, 33)
440 .filter(i -> i % 2 == 0)
441 .mapToObj(vp -> Arguments.of(np, vp)));
442 }
443
444 /**
445 * Test mutual exclusion of monitors with platform and virtual threads.
446 */
447 @ParameterizedTest
448 @MethodSource("threadCounts")
449 void testMutualExclusion(int nPlatformThreads, int nVirtualThreads) throws Exception {
450 class Counter {
|