f1c7afcc3f
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
200 lines
7.8 KiB
Java
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;
|
|
}
|
|
|
|
}
|