jdk-24/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java
Alan Bateman f1c7afcc3f 8306647: Implementation of Structured Concurrency (Preview)
8306572: Implementation of Scoped Values (Preview)

Co-authored-by: Alan Bateman <alanb@openjdk.org>
Co-authored-by: Andrew Haley <aph@openjdk.org>
Reviewed-by: psandoz, dfuchs, mchung
2023-06-07 06:41:09 +00:00

200 lines
7.8 KiB
Java

/*
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8284199 8296779 8306647
* @summary Test thread dumps with StructuredTaskScope
* @enablePreview
* @library /test/lib
* @run junit/othervm StructuredThreadDumpTest
*/
import java.util.concurrent.StructuredTaskScope;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Stream;
import com.sun.management.HotSpotDiagnosticMXBean;
import com.sun.management.HotSpotDiagnosticMXBean.ThreadDumpFormat;
import jdk.test.lib.threaddump.ThreadDump;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class StructuredThreadDumpTest {
/**
* Test that a thread dump with a tree of task scopes contains a thread grouping for
* each task scope.
*/
@Test
void testTree() throws Exception {
ThreadFactory factory = Thread.ofVirtual().factory();
try (var scope = new StructuredTaskScope<>("scope", factory)) {
Thread thread1 = fork(scope, "child-scope-A");
Thread thread2 = fork(scope, "child-scope-B");
try {
ThreadDump threadDump = threadDump();
// thread dump should have a thread container for each scope
var rootContainer = threadDump.rootThreadContainer();
var container1 = threadDump.findThreadContainer("scope").orElseThrow();
var container2 = threadDump.findThreadContainer("child-scope-A").orElseThrow();
var container3 = threadDump.findThreadContainer("child-scope-B").orElseThrow();
// check parents
assertFalse(rootContainer.parent().isPresent());
assertTrue(container1.parent().get() == rootContainer);
assertTrue(container2.parent().get() == container1);
assertTrue(container3.parent().get() == container1);
// check owners
assertFalse(rootContainer.owner().isPresent());
assertTrue(container1.owner().getAsLong() == Thread.currentThread().threadId());
assertTrue(container2.owner().getAsLong() == thread1.threadId());
assertTrue(container3.owner().getAsLong() == thread2.threadId());
// thread1 and threads2 should be in threads array of "scope"
container1.findThread(thread1.threadId()).orElseThrow();
container1.findThread(thread2.threadId()).orElseThrow();
} finally {
scope.shutdown();
scope.join();
}
}
}
/**
* Test that a thread dump with nested tasks scopes contains a thread grouping for
* each task scope.
*/
@Test
void testNested() throws Exception {
ThreadFactory factory = Thread.ofVirtual().factory();
try (var scope1 = new StructuredTaskScope<>("scope-A", factory)) {
Thread thread1 = fork(scope1);
try (var scope2 = new StructuredTaskScope<>("scope-B", factory)) {
Thread thread2 = fork(scope2);
try {
ThreadDump threadDump = threadDump();
// thread dump should have a thread container for both scopes
var rootContainer = threadDump.rootThreadContainer();
var container1 = threadDump.findThreadContainer("scope-A").orElseThrow();
var container2 = threadDump.findThreadContainer("scope-B").orElseThrow();
// check parents
assertFalse(rootContainer.parent().isPresent());
assertTrue(container1.parent().get() == rootContainer);
assertTrue(container2.parent().get() == container1);
// check owners
long tid = Thread.currentThread().threadId();
assertFalse(rootContainer.owner().isPresent());
assertTrue(container1.owner().getAsLong() == tid);
assertTrue(container2.owner().getAsLong() == tid);
// thread1 should be in threads array of "scope-A"
container1.findThread(thread1.threadId()).orElseThrow();
// thread2 should be in threads array of "scope-B"
container2.findThread(thread2.threadId()).orElseThrow();
} finally {
scope2.shutdown();
scope2.join();
}
} finally {
scope1.shutdown();
scope1.join();
}
}
}
/**
* Generates a JSON formatted thread dump to a temporary file, prints it to standard
* output, parses the JSON text and returns a ThreadDump object for the thread dump.
*/
private static ThreadDump threadDump() throws IOException {
Path dir = Path.of(".").toAbsolutePath();
Path file = Files.createTempFile(dir, "threadump", "json");
Files.delete(file);
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
.dumpThreads(file.toString(), ThreadDumpFormat.JSON);
try (Stream<String> stream = Files.lines(file)) {
stream.forEach(System.out::println);
}
String jsonText = Files.readString(file);
return ThreadDump.parse(jsonText);
}
/**
* Forks a subtask in the given scope that parks, returning the Thread that executes
* the subtask.
*/
private static Thread fork(StructuredTaskScope<Object> scope) throws Exception {
var ref = new AtomicReference<Thread>();
scope.fork(() -> {
ref.set(Thread.currentThread());
LockSupport.park();
return null;
});
Thread thread;
while ((thread = ref.get()) == null) {
Thread.sleep(10);
}
return thread;
}
/**
* Forks a subtask in the given scope. The subtask creates a new child scope with
* the given name, then parks. This method returns Thread that executes the subtask.
*/
private static Thread fork(StructuredTaskScope<Object> scope,
String childScopeName) throws Exception {
var ref = new AtomicReference<Thread>();
scope.fork(() -> {
ThreadFactory factory = Thread.ofVirtual().factory();
try (var childScope = new StructuredTaskScope<Object>(childScopeName, factory)) {
ref.set(Thread.currentThread());
LockSupport.park();
}
return null;
});
Thread thread;
while ((thread = ref.get()) == null) {
Thread.sleep(10);
}
return thread;
}
}