1 /* 2 * Copyright (c) 2022, 2024, 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 8284199 8296779 8306647 27 * @summary Test thread dumps with StructuredTaskScope 28 * @enablePreview 29 * @library /test/lib 30 * @run junit/othervm StructuredThreadDumpTest 31 */ 32 33 import java.util.concurrent.StructuredTaskScope; 34 import java.util.concurrent.StructuredTaskScope.Joiner; 35 import java.io.IOException; 36 import java.lang.management.ManagementFactory; 37 import java.nio.file.Files; 38 import java.nio.file.Path; 39 import java.util.concurrent.ThreadFactory; 40 import java.util.concurrent.atomic.AtomicReference; 41 import java.util.concurrent.locks.LockSupport; 42 import java.util.stream.Stream; 43 import com.sun.management.HotSpotDiagnosticMXBean; 44 import com.sun.management.HotSpotDiagnosticMXBean.ThreadDumpFormat; 45 import jdk.test.lib.threaddump.ThreadDump; 46 import org.junit.jupiter.api.Test; 47 import static org.junit.jupiter.api.Assertions.*; 48 49 class StructuredThreadDumpTest { 50 51 /** 52 * Test that a thread dump with a tree of task scopes contains a thread grouping for 53 * each task scope. 54 */ 55 @Test 56 void testTree() throws Exception { 57 try (var scope = StructuredTaskScope.open(Joiner.awaitAll(), cf -> cf.withName("scope"))) { 58 Thread thread1 = fork(scope, "child-scope-A"); 59 Thread thread2 = fork(scope, "child-scope-B"); 60 try { 61 ThreadDump threadDump = threadDump(); 62 63 // thread dump should have a thread container for each scope 64 var rootContainer = threadDump.rootThreadContainer(); 65 var container1 = threadDump.findThreadContainer("scope").orElseThrow(); 66 var container2 = threadDump.findThreadContainer("child-scope-A").orElseThrow(); 67 var container3 = threadDump.findThreadContainer("child-scope-B").orElseThrow(); 68 69 // check parents 70 assertFalse(rootContainer.parent().isPresent()); 71 assertTrue(container1.parent().get() == rootContainer); 72 assertTrue(container2.parent().get() == container1); 73 assertTrue(container3.parent().get() == container1); 74 75 // check owners 76 assertFalse(rootContainer.owner().isPresent()); 77 assertTrue(container1.owner().getAsLong() == Thread.currentThread().threadId()); 78 assertTrue(container2.owner().getAsLong() == thread1.threadId()); 79 assertTrue(container3.owner().getAsLong() == thread2.threadId()); 80 81 // thread1 and threads2 should be in threads array of "scope" 82 container1.findThread(thread1.threadId()).orElseThrow(); 83 container1.findThread(thread2.threadId()).orElseThrow(); 84 85 } finally { 86 LockSupport.unpark(thread1); 87 LockSupport.unpark(thread2); 88 scope.join(); 89 } 90 } 91 } 92 93 /** 94 * Test that a thread dump with nested tasks scopes contains a thread grouping for 95 * each task scope. 96 */ 97 @Test 98 void testNested() throws Exception { 99 try (var scope1 = StructuredTaskScope.open(Joiner.awaitAll(), cf -> cf.withName("scope-A"))) { 100 Thread thread1 = fork(scope1); 101 102 try (var scope2 = StructuredTaskScope.open(Joiner.awaitAll(), cf -> cf.withName("scope-B"))) { 103 Thread thread2 = fork(scope2); 104 try { 105 ThreadDump threadDump = threadDump(); 106 107 // thread dump should have a thread container for both scopes 108 var rootContainer = threadDump.rootThreadContainer(); 109 var container1 = threadDump.findThreadContainer("scope-A").orElseThrow(); 110 var container2 = threadDump.findThreadContainer("scope-B").orElseThrow(); 111 112 // check parents 113 assertFalse(rootContainer.parent().isPresent()); 114 assertTrue(container1.parent().get() == rootContainer); 115 assertTrue(container2.parent().get() == container1); 116 117 // check owners 118 long tid = Thread.currentThread().threadId(); 119 assertFalse(rootContainer.owner().isPresent()); 120 assertTrue(container1.owner().getAsLong() == tid); 121 assertTrue(container2.owner().getAsLong() == tid); 122 123 // thread1 should be in threads array of "scope-A" 124 container1.findThread(thread1.threadId()).orElseThrow(); 125 126 // thread2 should be in threads array of "scope-B" 127 container2.findThread(thread2.threadId()).orElseThrow(); 128 129 } finally { 130 LockSupport.unpark(thread2); 131 scope2.join(); 132 } 133 } finally { 134 LockSupport.unpark(thread1); 135 scope1.join(); 136 } 137 } 138 } 139 140 /** 141 * Generates a JSON formatted thread dump to a temporary file, prints it to standard 142 * output, parses the JSON text and returns a ThreadDump object for the thread dump. 143 */ 144 private static ThreadDump threadDump() throws IOException { 145 Path dir = Path.of(".").toAbsolutePath(); 146 Path file = Files.createTempFile(dir, "threadump", "json"); 147 Files.delete(file); 148 ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class) 149 .dumpThreads(file.toString(), ThreadDumpFormat.JSON); 150 151 try (Stream<String> stream = Files.lines(file)) { 152 stream.forEach(System.out::println); 153 } 154 155 String jsonText = Files.readString(file); 156 return ThreadDump.parse(jsonText); 157 } 158 159 /** 160 * Forks a subtask in the given scope that parks, returning the Thread that executes 161 * the subtask. 162 */ 163 private static Thread fork(StructuredTaskScope<Object, Void> scope) throws Exception { 164 var ref = new AtomicReference<Thread>(); 165 scope.fork(() -> { 166 ref.set(Thread.currentThread()); 167 LockSupport.park(); 168 return null; 169 }); 170 Thread thread; 171 while ((thread = ref.get()) == null) { 172 Thread.sleep(10); 173 } 174 return thread; 175 } 176 177 /** 178 * Forks a subtask in the given scope. The subtask creates a new child scope with 179 * the given name, then parks. This method returns Thread that executes the subtask. 180 */ 181 private static Thread fork(StructuredTaskScope<Object, Void> scope, 182 String childScopeName) throws Exception { 183 var ref = new AtomicReference<Thread>(); 184 scope.fork(() -> { 185 try (var childScope = StructuredTaskScope.open(Joiner.awaitAll(), 186 cf -> cf.withName(childScopeName))) { 187 ref.set(Thread.currentThread()); 188 LockSupport.park(); 189 } 190 }); 191 Thread thread; 192 while ((thread = ref.get()) == null) { 193 Thread.sleep(10); 194 } 195 return thread; 196 } 197 198 }