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 }