Runnable demos for the ankurm.com structured-concurrency-java article: the ExecutorService thread leak and its fix, every built-in Joiner, structure violations, timeouts, a custom Joiner, JSON thread dumps (in-process and jcmd), scoped values, javap diffs of the API on 25, 26, 27 and 28-ea, compiler output for six pieces of 25/26-era code, and the preview class-file trap between 27 and 28. Transcripts in output/. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01CifR2BE7PBvRy5hT8TS9oz
54 lines
2.9 KiB
Java
54 lines
2.9 KiB
Java
import com.sun.management.HotSpotDiagnosticMXBean;
|
|
import java.lang.management.ManagementFactory;
|
|
import java.nio.file.*;
|
|
import java.util.concurrent.*;
|
|
import java.util.concurrent.StructuredTaskScope.Joiner;
|
|
|
|
/** The thread dump shows the scope as a container, its owner, and the threads forked in it. */
|
|
public class ThreadDump {
|
|
public static void main(String[] args) throws Exception {
|
|
if (args.length > 0 && args[0].equals("hold")) { // used by run.sh: hold nested scopes open so jcmd can dump them
|
|
try (var scope = StructuredTaskScope.open(Joiner.<String>awaitAllSuccessfulOrThrow(), cf -> cf.withName("orders"))) {
|
|
scope.fork(() -> { Thread.sleep(6000); return "A"; });
|
|
scope.fork(() -> { // subtask B opens a child scope of its own
|
|
try (var child = StructuredTaskScope.open(Joiner.<String>awaitAllSuccessfulOrThrow(), cf -> cf.withName("inventory"))) {
|
|
child.fork(() -> { Thread.sleep(6000); return "B1"; });
|
|
child.join();
|
|
}
|
|
return "B";
|
|
});
|
|
scope.join();
|
|
}
|
|
return;
|
|
}
|
|
Path file = Files.createTempFile("dump", ".json"); Files.delete(file);
|
|
try (var scope = StructuredTaskScope.open(Joiner.<String>awaitAllSuccessfulOrThrow(), cf -> cf.withName("orders"))) {
|
|
for (int i = 0; i < 2; i++) {
|
|
int n = i;
|
|
scope.fork(() -> { Thread.sleep(1500); return "task-" + n; });
|
|
}
|
|
Thread.sleep(300); // let the subtasks start and block
|
|
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class)
|
|
.dumpThreads(file.toString(), HotSpotDiagnosticMXBean.ThreadDumpFormat.JSON);
|
|
scope.join();
|
|
}
|
|
String json = Files.readString(file);
|
|
int at = json.indexOf("\"container\": \"orders");
|
|
int start = json.lastIndexOf('{', at);
|
|
int end = json.indexOf("\"threadCount\"", at);
|
|
String block = json.substring(start, end);
|
|
// identity hashes, thread ids and timestamps change between runs: mask them so the transcript is stable
|
|
block = block.replaceAll("@[0-9a-f]+", "@<hash>").replaceAll("\"tid\": \\d+", "\"tid\": <n>").replaceAll("\"owner\": \\d+", "\"owner\": <n>");
|
|
// keep the lines that show the hierarchy: the scope, its parent and owner, and each forked thread with its own frame
|
|
for (String line : block.split("\n")) {
|
|
String l = line.trim();
|
|
if (l.startsWith("\"container\"") || l.startsWith("\"parent\"") || l.startsWith("\"owner\"")
|
|
|| l.startsWith("\"tid\"") || l.startsWith("\"virtual\"") || l.startsWith("\"state\"")
|
|
|| l.contains("ThreadDump.lambda")) {
|
|
System.out.println(l);
|
|
}
|
|
}
|
|
Files.deleteIfExists(file);
|
|
}
|
|
}
|