#!/usr/bin/env bash # Structured concurrency, final in JDK 28 (JEP 543). Regenerates every file in output/. # JDK25=/path JDK26=/path JDK27=/path JDK28=/path ./run.sh # JDK 28 is an early-access build at the time of writing (GA proposed 2027-03-09); JDK 27 still treats the API as a preview. set -uo pipefail unset JAVA_TOOL_OPTIONS HERE="$(cd "$(dirname "$0")" && pwd)" JDK25="${JDK25:-/opt/jdks/jdk25}"; JDK26="${JDK26:-/opt/jdks/jdk26}"; JDK27="${JDK27:-/opt/jdks/jdk27}"; JDK28="${JDK28:-/opt/jdks/jdk28}" OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT cd "$HERE" rel() { case "$1" in "$JDK25") echo 25;; "$JDK26") echo 26;; "$JDK27") echo 27;; *) echo 28;; esac; } t() { "$1/bin/java" -version 2>&1 | sed -n 2p | sed 's/.*(build \(.*\))/\1/'; } mk() { mktemp -d -p "$B"; } # plain compile + run (no flags) cr() { local jdk="$1" src="$2" cls="$3" o; o="$(mk)" echo "\$ javac $src && java $cls ($(t "$jdk"))" "$jdk/bin/javac" --add-modules jdk.management -d "$o" "$src" 2>&1 && "$jdk/bin/java" --add-modules jdk.management -cp "$o" "$cls" 2>&1; } # preview compile + run crp() { local jdk="$1" src="$2" cls="$3" o r; o="$(mk)"; r="$(rel "$jdk")" echo "\$ javac --enable-preview --release $r $src && java --enable-preview $cls ($(t "$jdk"))" "$jdk/bin/javac" --enable-preview --release "$r" --add-modules jdk.management -d "$o" "$src" 2>&1 | grep -v '^Note:' "$jdk/bin/java" --enable-preview --add-modules jdk.management -cp "$o" "$cls" 2>&1; } # compile only, no flags (prints the compiler's own message and the exit status) co() { local jdk="$1" src="$2" o; o="$(mk)" echo "\$ javac $src ($(t "$jdk"))" "$jdk/bin/javac" -d "$o" "$src" 2>&1 | grep -v '^Note:'; echo "exit=${PIPESTATUS[0]}"; } # compile only, with --enable-preview for the JDK's own release cop() { local jdk="$1" src="$2" o r; o="$(mk)"; r="$(rel "$jdk")" echo "\$ javac --enable-preview --release $r $src ($(t "$jdk"))" "$jdk/bin/javac" --enable-preview --release "$r" -d "$o" "$src" 2>&1 | grep -v '^Note:'; echo "exit=${PIPESTATUS[0]}"; } # 01 - the canonical program: final on 28 (no flags), preview on 27 (flags), and without the flag on 27 { cr "$JDK28" src/Server.java Server; echo; crp "$JDK27" src/Server.java Server; echo; co "$JDK27" src/Server.java; } > "$OUT/01-server.txt" # 02 - the failure that motivates the feature, then the same subtasks in a scope { cr "$JDK28" src/Unstructured.java Unstructured; echo; cr "$JDK28" src/FailFast.java FailFast; } > "$OUT/02-unstructured-vs-structured.txt" # 03 - the built-in joiners { cr "$JDK28" src/Joiners.java Joiners; } > "$OUT/03-joiners.txt" # 04 - structure violations and failure handling { cr "$JDK28" src/Failures.java Failures; } > "$OUT/04-failures.txt" # 05 - timeouts and a custom joiner { cr "$JDK28" src/Timeouts.java Timeouts; echo; cr "$JDK28" src/CollectingJoiner.java CollectingJoiner; } > "$OUT/05-timeouts-and-custom-joiner.txt" # 06 - the thread dump { cr "$JDK28" src/ThreadDump.java ThreadDump; } > "$OUT/06-thread-dump.txt" # 07 - scoped values { cr "$JDK28" src/ScopedValues.java ScopedValues; } > "$OUT/07-scoped-values.txt" # 08 - the API surface, release by release (javap of java.base) api() { local jdk="$1"; echo "# javap java.util.concurrent.StructuredTaskScope and its nested types ($(t "$jdk"))" for c in 'StructuredTaskScope' 'StructuredTaskScope$Joiner' 'StructuredTaskScope$Configuration' 'StructuredTaskScope$Subtask' 'StructuredTaskScope$Subtask$State'; do "$jdk/bin/javap" --module java.base "java.util.concurrent.$c" 2>&1; done | sed 's/java\.util\.concurrent\.//g;s/java\.lang\.//g;s/java\.util\.function\.//g;s/java\.util\.//g'; } { api "$JDK28"; } > "$OUT/08-api-28.txt" for v in 25 26 27 28; do eval "j=\$JDK$v"; api "$j" > "$B/api$v.txt"; done { for p in "25 26" "26 27" "27 28"; do set -- $p echo "\$ diff api-$1 api-$2 (signature lines only; the first line of each file names the build)" if diff <(tail -n +2 "$B/api$1.txt") <(tail -n +2 "$B/api$2.txt") > "$B/d.txt"; then echo "(no difference)" else grep -v '^[0-9,]*[acd][0-9,]*$' "$B/d.txt" | grep -v '^---$'; fi; echo; done for v in 25 26 27 28; do eval "j=\$JDK$v"; echo "\$ jimage list modules | grep -E 'FailedException|TimeoutException|CancelledByTimeout' ($(t "$j"))" "$j/bin/jimage" list "$j/lib/modules" 2>/dev/null | grep -E 'StructuredTaskScope\$(FailedException|TimeoutException|CancelledByTimeoutException)' | sed 's/^ *//'; echo; done for v in 26 27 28; do eval "j=\$JDK$v"; echo "\$ javap -v StructuredTaskScope | grep 'PreviewFeature(' -A1 ($(t "$j"))" "$j/bin/javap" -v --module java.base java.util.concurrent.StructuredTaskScope 2>&1 | grep -A1 'jdk.internal.javac.PreviewFeature(' | head -2 || true "$j/bin/javap" -v --module java.base java.util.concurrent.StructuredTaskScope 2>&1 | grep -q 'PreviewFeature(' || echo "(no @PreviewFeature annotation: the API is final)"; echo; done } > "$OUT/09-api-diff.txt" # 10 - code written for 25 or 26 against 27 and 28 (the compiler's own words) { for f in MissingCatch OldFailedException OldTimeoutException AwaitAll OldJoiner ShutdownOnFailure; do for j in "$JDK26" "$JDK28"; do if [ "$j" = "$JDK26" ]; then cop "$j" broken/$f.java; else co "$j" broken/$f.java; fi; echo; done; done echo "# the same sources on JDK 25 for ShutdownOnFailure"; cop "$JDK25" broken/ShutdownOnFailure.java; } > "$OUT/10-broken-old-api.txt" # 11 - the preview-flag traps { O="$(mk)" echo "# compile on 27 with preview, then run the class file on 27 and on 28" "$JDK27/bin/javac" --enable-preview --release 27 -d "$O" src/Server.java 2>&1 | grep -v '^Note:' echo "\$ java --enable-preview Server ($(t "$JDK27"))"; "$JDK27/bin/java" --enable-preview -cp "$O" Server 2>&1 echo "\$ java --enable-preview Server ($(t "$JDK28"))"; "$JDK28/bin/java" --enable-preview -cp "$O" Server 2>&1 echo "\$ java Server ($(t "$JDK27"))"; "$JDK27/bin/java" -cp "$O" Server 2>&1 echo; echo "# the class file's minor version" "$JDK28/bin/javap" -v -cp "$O" Server 2>&1 | grep -E 'minor version|major version' echo; echo "# javac 28 asked for --release 27" echo "\$ javac --release 27 src/Server.java ($(t "$JDK28"))"; "$JDK28/bin/javac" --release 27 -d "$(mk)" src/Server.java 2>&1 | grep -v '^Note:' echo; echo "# javac 28 with --enable-preview --release 28 on the same file (nothing in it needs preview)" echo "\$ javac --enable-preview --release 28 src/Server.java ($(t "$JDK28"))"; "$JDK28/bin/javac" --enable-preview --release 28 -d "$(mk)" src/Server.java 2>&1; echo "exit=$?" echo; echo "# where that Note comes from: the first lines of -Xlint:preview" echo "\$ javac --enable-preview --release 28 -Xlint:preview src/Server.java 2>&1 | head -3 ($(t "$JDK28"))" "$JDK28/bin/javac" --enable-preview --release 28 -Xlint:preview -d "$(mk)" src/Server.java 2>&1 | head -3 echo; echo "# recompile on 28 without any flag and run on 28" cr "$JDK28" src/Server.java Server } > "$OUT/11-preview-traps.txt" # 12 - the short fragments quoted in the article compile and run on 28 { cr "$JDK28" src/Snippets.java Snippets; echo; cr "$JDK28" src/QuickReference.java QuickReference; } > "$OUT/12-snippets.txt" # 13 - five seeded random tasks: one run where all succeed, one where a task is too slow { o="$(mk)"; "$JDK28/bin/javac" -d "$o" src/RandomTasks.java 2>&1 for seed in 5 3; do echo "\$ java RandomTasks $seed ($(t "$JDK28"))"; "$JDK28/bin/java" -cp "$o" RandomTasks $seed 2>&1; echo; done; } > "$OUT/13-random-tasks.txt" # 14 - a subtask that ignores interrupts keeps close() waiting { cr "$JDK28" src/StuckClose.java StuckClose; } > "$OUT/14-stuck-close.txt" # 15 - the thread dump taken from outside with jcmd, as the JEP describes it { o="$(mk)"; "$JDK28/bin/javac" --add-modules jdk.management -d "$o" src/ThreadDump.java 2>&1 "$JDK28/bin/java" -cp "$o" ThreadDump hold > /dev/null 2>&1 & pid=$!; sleep 2 echo "\$ jcmd Thread.dump_to_file -format=json threads.json ($(t "$JDK28"))" "$JDK28/bin/jcmd" "$pid" Thread.dump_to_file -format=json "$B/threads.json" 2>&1 | sed -E 's#'"$B"'##; s/^[0-9]+:$/:/' kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null echo; echo "# the scope \"orders\" and its child scope in threads.json (stack frames, timestamps and ids trimmed or masked)" awk '/"container": "orders/{f=1} f' "$B/threads.json" | sed -E 's/@[0-9a-f]+/@/; s/"(tid|owner)": [0-9]+/"\1": /' | grep -E '"(container|parent|owner|tid|virtual|state)"|ThreadDump.lambda' | sed -E 's/^ +//' } > "$OUT/15-jcmd-thread-dump.txt" echo done