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
 26  * @bug 8356870
 27  * @summary Test HotSpotDiagnosticMXBean.dumpThreads with a thread owning a monitor for
 28  *     an object that is scalar replaced
 29  * @requires !vm.debug & (vm.compMode != "Xcomp")
 30  * @requires (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4)
 31  * @modules jdk.management
 32  * @library /test/lib
 33  * @run main/othervm DumpThreadsWithEliminatedLock plain platform
 34  * @run main/othervm DumpThreadsWithEliminatedLock plain virtual
 35  * @run main/othervm DumpThreadsWithEliminatedLock json platform
 36  * @run main/othervm DumpThreadsWithEliminatedLock json virtual
 37  */
 38 
 39 import java.io.IOException;
 40 import java.io.UncheckedIOException;
 41 import java.lang.management.ManagementFactory;
 42 import java.nio.file.Files;
 43 import java.nio.file.Path;
 44 import java.time.Instant;
 45 import java.util.List;
 46 import java.util.concurrent.ThreadFactory;
 47 import java.util.concurrent.atomic.AtomicBoolean;
 48 import java.util.concurrent.atomic.AtomicReference;
 49 import java.util.stream.Stream;
 50 import com.sun.management.HotSpotDiagnosticMXBean;
 51 import jdk.test.lib.threaddump.ThreadDump;
 52 import jdk.test.lib.thread.VThreadRunner;
 53 
 54 public class DumpThreadsWithEliminatedLock {
 55 
 56     public static void main(String[] args) throws Exception {
 57         boolean plain = switch (args[0]) {
 58             case "plain" -> true;
 59             case "json"  -> false;
 60             default      -> throw new RuntimeException("Unknown dump format");
 61         };
 62 
 63         ThreadFactory factory = switch (args[1]) {
 64             case "platform" -> Thread.ofPlatform().factory();
 65             case "virtual"  -> Thread.ofVirtual().factory();
 66             default         -> throw new RuntimeException("Unknown thread kind");
 67         };
 68 
 69         // need at least two carriers for JTREG_TEST_THREAD_FACTORY=Virtual
 70         if (Thread.currentThread().isVirtual()) {
 71             VThreadRunner.ensureParallelism(2);
 72         }
 73 
 74         // A thread that spins creating and adding to a StringBuffer. StringBuffer is
 75         // synchronized, assume object will be scalar replaced and the lock eliminated.
 76         var done = new AtomicBoolean();
 77         var ref = new AtomicReference<String>();
 78         Thread thread = factory.newThread(() -> {
 79             while (!done.get()) {
 80                 StringBuffer sb = new StringBuffer();
 81                 sb.append(System.currentTimeMillis());
 82                 String s = sb.toString();
 83                 ref.set(s);
 84             }
 85         });
 86         try {
 87             thread.start();
 88             if (plain) {
 89                 testPlainFormat();
 90             } else {
 91                 testJsonFormat(thread.threadId());
 92             }
 93         } finally {
 94             done.set(true);
 95             thread.join();
 96         }
 97     }
 98 
 99     /**
100      * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in plain text
101      * format until "lock is eliminated" is found in the output.
102      */
103     private static void testPlainFormat() {
104         try {
105             Path file = genOutputPath(".txt");
106             boolean found = false;
107             int attempts = 0;
108             while (!found) {
109                 attempts++;
110                 Files.deleteIfExists(file);
111                 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
112                         .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.TEXT_PLAIN);
113                 try (Stream<String> stream = Files.lines(file)) {
114                     found = stream.map(String::trim)
115                             .anyMatch(l -> l.contains("- lock is eliminated"));
116                 }
117                 System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found);
118             }
119         } catch (IOException ioe) {
120             throw new UncheckedIOException(ioe);
121         }
122     }
123 
124     /**
125      * Invoke HotSpotDiagnosticMXBean.dumpThreads to generate a thread dump in JSON format
126      * until the monitorsOwned.locks array for the given thread has a null lock.
127      */
128     private static void testJsonFormat(long tid) {
129         try {
130             Path file = genOutputPath(".json");
131             boolean found = false;
132             int attempts = 0;
133             while (!found) {
134                 attempts++;
135                 Files.deleteIfExists(file);
136                 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
137                         .dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON);
138 
139                 // parse thread dump as JSON and find thread
140                 String jsonText = Files.readString(file);
141                 ThreadDump threadDump = ThreadDump.parse(jsonText);
142                 ThreadDump.ThreadInfo ti = threadDump.rootThreadContainer()
143                         .findThread(tid)
144                         .orElse(null);
145                 if (ti == null) {
146                     throw new RuntimeException("Thread " + tid + " not found in thread dump");
147                 }
148 
149                 // look for null element in ownedMonitors/locks array
150                 found = ti.ownedMonitors()
151                         .values()
152                         .stream()
153                         .flatMap(List::stream)
154                         .anyMatch(o -> o == null);
155                 System.out.format("%s Attempt %d, found: %b%n", Instant.now(), attempts, found);
156             }
157         } catch (IOException ioe) {
158             throw new UncheckedIOException(ioe);
159         }
160     }
161 
162     /**
163      * Generate a file path with the given suffix to use as an output file.
164      */
165     private static Path genOutputPath(String suffix) throws IOException {
166         Path dir = Path.of(".").toAbsolutePath();
167         Path file = Files.createTempFile(dir, "dump", suffix);
168         Files.delete(file);
169         return file;
170     }
171 }