1 /*
2 * Copyright (c) 2025, 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=transitions
26 * @bug 8364343
27 * @summary HotSpotDiagnosticMXBean.dumpThreads while virtual threads are parking and unparking
28 * @requires vm.continuations
29 * @modules jdk.management
30 * @library /test/lib
31 * @run main/othervm/timeout=200 DumpThreadsWhenParking 1000 1 100
32 */
33
34 /*
35 * @test id=concurrent
36 * @summary HotSpotDiagnosticMXBean.dumpThreads from concurrent threads while virtual threads
37 * are parking and unparking
38 * @requires vm.continuations
39 * @modules jdk.management
40 * @library /test/lib
41 * @run main/othervm/timeout=200 DumpThreadsWhenParking 100 4 100
42 */
43
44 import java.lang.management.ManagementFactory;
45 import java.nio.file.Files;
46 import java.nio.file.Path;
47 import java.time.Instant;
48 import java.util.List;
49 import java.util.Objects;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.Future;
52 import java.util.concurrent.Phaser;
53 import java.util.concurrent.atomic.AtomicBoolean;
54 import java.util.concurrent.locks.LockSupport;
55 import java.util.stream.IntStream;
56 import com.sun.management.HotSpotDiagnosticMXBean;
57 import jdk.test.lib.thread.VThreadRunner;
58 import jdk.test.lib.threaddump.ThreadDump;
59
60 public class DumpThreadsWhenParking {
61
62 public static void main(String... args) throws Throwable {
63 int vthreadCount = Integer.parseInt(args[0]);
64 int concurrentDumpers = Integer.parseInt(args[1]);
65 int iterations = Integer.parseInt(args[2]);
66
67 // need >=2 carriers to make progress
68 VThreadRunner.ensureParallelism(2);
69
70 try (var executor = Executors.newVirtualThreadPerTaskExecutor();
71 var pool = Executors.newCachedThreadPool()) {
72
73 // start virtual threads that park and unpark
74 var done = new AtomicBoolean();
75 var phaser = new Phaser(vthreadCount + 1);
76 for (int i = 0; i < vthreadCount; i++) {
77 executor.submit(() -> {
78 phaser.arriveAndAwaitAdvance();
79 while (!done.get()) {
80 LockSupport.parkNanos(1);
81 }
82 });
83 }
84 // wait for all virtual threads to start so all have a non-empty stack
85 System.out.format("Waiting for %d virtual threads to start ...%n", vthreadCount);
86 phaser.arriveAndAwaitAdvance();
87 System.out.format("%d virtual threads started.%n", vthreadCount);
88
89 // Bash on HotSpotDiagnosticMXBean.dumpThreads from >= 1 threads
90 try {
91 String containerName = Objects.toIdentityString(executor);
92 for (int i = 1; i <= iterations; i++) {
93 System.out.format("%s %d of %d ...%n", Instant.now(), i, iterations);
94 List<Future<Void>> futures = IntStream.of(0, concurrentDumpers)
95 .mapToObj(_ -> pool.submit(() -> dumpThreads(containerName, vthreadCount)))
96 .toList();
97 for (Future<?> future : futures) {
98 future.get();
99 }
100 }
101 } finally {
102 done.set(true);
103 }
104 }
105 }
106
107 /**
108 * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump to a file in
109 * JSON format. Parse the thread dump to ensure it contains a thread grouping with
110 * the expected number of virtual threads.
111 */
112 static Void dumpThreads(String containerName, int expectedVThreadCount) throws Exception {
113 long tid = Thread.currentThread().threadId();
114 Path file = Path.of("threads-" + tid + ".json").toAbsolutePath();
115 Files.deleteIfExists(file);
116 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
117 .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON);
118
119 // read and parse the dump
120 String jsonText = Files.readString(file);
121 ThreadDump threadDump = ThreadDump.parse(jsonText);
122 var container = threadDump.findThreadContainer(containerName).orElse(null);
123 if (container == null) {
124 fail(containerName + " not found in thread dump");
125 }
126
127 // check expected virtual thread count
128 long threadCount = container.threads().count();
129 if (threadCount != expectedVThreadCount) {
130 fail(threadCount + " virtual threads found, expected " + expectedVThreadCount);
131 }
132
133 // check each thread is a virtual thread with stack frames
134 container.threads().forEach(t -> {
135 if (!t.isVirtual()) {
136 fail("#" + t.tid() + "(" + t.name() + ") is not a virtual thread");
137 }
138 long stackFrameCount = t.stack().count();
139 if (stackFrameCount == 0) {
140 fail("#" + t.tid() + " has empty stack");
141 }
142 });
143 return null;
144 }
145
146 private static void fail(String message) {
147 throw new RuntimeException(message);
148 }
149 }