Add structured-concurrency-final module: StructuredTaskScope final in JDK 28 (JEP 543), run on 28-ea, 27, 26 and 25

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
This commit is contained in:
Claude
2026-10-26 02:01:31 +00:00
parent 2e25042a68
commit c93a1fffbb
37 changed files with 1351 additions and 0 deletions
@@ -0,0 +1,35 @@
$ javac src/Server.java && java Server (28-beta+21-ea)
Response[user=Alice, order=42]
Runtime: 28-beta+21-ea
$ javac --enable-preview --release 27 src/Server.java && java --enable-preview Server (27.0.1.0.2+1)
Response[user=Alice, order=42]
Runtime: 27.0.1.0.2+1
$ javac src/Server.java (27.0.1.0.2+1)
src/Server.java:2: error: StructuredTaskScope is a preview API and is disabled by default.
import java.util.concurrent.StructuredTaskScope;
^
(use --enable-preview to enable preview APIs)
src/Server.java:3: error: StructuredTaskScope is a preview API and is disabled by default.
import java.util.concurrent.StructuredTaskScope.Subtask;
^
(use --enable-preview to enable preview APIs)
src/Server.java:3: error: Subtask is a preview API and is disabled by default.
import java.util.concurrent.StructuredTaskScope.Subtask;
^
(use --enable-preview to enable preview APIs)
src/Server.java:22: error: StructuredTaskScope is a preview API and is disabled by default.
try (var scope = StructuredTaskScope.open()) { // 1. open
^
(use --enable-preview to enable preview APIs)
src/Server.java:23: error: Subtask is a preview API and is disabled by default.
Subtask<String> user = scope.fork(this::findUser); // 2. fork
^
(use --enable-preview to enable preview APIs)
src/Server.java:24: error: Subtask is a preview API and is disabled by default.
Subtask<Integer> order = scope.fork(this::fetchOrder); // fork
^
(use --enable-preview to enable preview APIs)
6 errors
exit=1
@@ -0,0 +1,10 @@
$ javac src/Unstructured.java && java Unstructured (28-beta+21-ea)
handle() failed: java.lang.IllegalStateException: user service down
handle() has returned. Waiting 400 ms to see what the other thread does:
fetchOrder() finished, although nobody wants the answer any more
$ javac src/FailFast.java && java FailFast (28-beta+21-ea)
fetchOrder() was interrupted
handle() failed: java.lang.IllegalStateException: user service down
handle() has returned. Waiting 400 ms to see what the other thread does:
(nothing more happened)
@@ -0,0 +1,21 @@
$ javac src/Joiners.java && java Joiners (28-beta+21-ea)
-- allSuccessfulOrThrow(): List in FORK order, not completion order
[slow, fast, medium]
-- allSuccessfulOrThrow(): one failure cancels the others, join() throws ExecutionException
ExecutionException, cause = java.lang.IllegalStateException: broken failed
interrupted: [slow]
-- anySuccessfulOrThrow(): the race; losers are interrupted
winner: B-50ms
interrupted: [A-300ms, C-300ms]
-- anySuccessfulOrThrow(): when every subtask fails
ExecutionException, cause = java.lang.IllegalStateException: one failed
-- awaitAllSuccessfulOrThrow(): returns null (Void), like the zero-argument open()
join() = null, a=a, b=b
-- allUntil(predicate): stop when two subtasks have completed
SUCCESS t1
SUCCESS t2
UNAVAILABLE
UNAVAILABLE
interrupted: [t3, t4]
-- the Function overload: join() throws OUR exception type
caught ServiceException: service call failed: orders failed
@@ -0,0 +1,15 @@
$ javac src/Failures.java && java Failures (28-beta+21-ea)
-- Subtask.get() before join()
java.lang.IllegalStateException: null
-- leaving the try block without join()
java.lang.IllegalStateException: Owner did not join after forking
-- fork() from a thread that does not own the scope
returned java.lang.WrongThreadException: Current thread not owner
-- closing scopes out of order (not try-with-resources)
java.util.concurrent.StructureViolationException: null
-- join() twice
java.lang.IllegalStateException: Already joined or scope is closed
-- the failure surfaces as ExecutionException; pattern-match the cause
I/O failed: disk gone
-- two subtasks fail: which cause do you get?
cause = first (the first to fail wins)
@@ -0,0 +1,15 @@
$ javac src/Timeouts.java && java Timeouts (28-beta+21-ea)
-- inside the deadline
[fast, fast]
-- deadline 200 ms, one subtask needs 5 s
ExecutionException, cause = java.util.concurrent.StructuredTaskScope$CancelledByTimeoutException
returned within well under a second (not 5 s): the slow subtask was interrupted
-- the same deadline with a joiner whose timeout() returns a partial result
join() = [fast]
-- a thread factory decides the subtask thread's name
[order-0, order-1]
$ javac src/CollectingJoiner.java && java CollectingJoiner (28-beta+21-ea)
collected (sorted): [1, 3]
-- with a 100 ms timeout and a 5 s subtask
CompletionException, cause = java.util.concurrent.StructuredTaskScope$CancelledByTimeoutException
@@ -0,0 +1,12 @@
$ javac src/ThreadDump.java && java ThreadDump (28-beta+21-ea)
"container": "orders\/jdk.internal.misc.ThreadFlock$ThreadContainerImpl@<hash>",
"parent": "<root>",
"owner": <n>,
"tid": <n>,
"virtual": true,
"state": "TIMED_WAITING",
"ThreadDump.lambda$main$6(ThreadDump.java:28)",
"tid": <n>,
"virtual": true,
"state": "TIMED_WAITING",
"ThreadDump.lambda$main$6(ThreadDump.java:28)",
@@ -0,0 +1,4 @@
$ javac src/ScopedValues.java && java ScopedValues (28-beta+21-ea)
forked subtask sees req-7731
plain thread: bound? false
outside the binding: bound? false
@@ -0,0 +1,48 @@
# javap java.util.concurrent.StructuredTaskScope and its nested types (28-beta+21-ea)
Compiled from "StructuredTaskScope.java"
public interface StructuredTaskScope<T, R, R_X extends Throwable> extends AutoCloseable {
public static <T, R, R_X extends Throwable> StructuredTaskScope<T, R, R_X> open(StructuredTaskScope$Joiner<? super T, ? extends R, R_X>, UnaryOperator<StructuredTaskScope$Configuration>);
public static <T, R, R_X extends Throwable> StructuredTaskScope<T, R, R_X> open(StructuredTaskScope$Joiner<? super T, ? extends R, R_X>);
public static <T> StructuredTaskScope<T, Void, ExecutionException> open(UnaryOperator<StructuredTaskScope$Configuration>);
public static <T> StructuredTaskScope<T, Void, ExecutionException> open();
public abstract <U extends T> StructuredTaskScope$Subtask<U> fork(Callable<? extends U>);
public abstract <U extends T> StructuredTaskScope$Subtask<U> fork(Runnable);
public abstract R join() throws R_X, InterruptedException;
public abstract boolean isCancelled();
public abstract void close();
}
Compiled from "StructuredTaskScope.java"
public interface StructuredTaskScope$Joiner<T, R, R_X extends Throwable> {
public default boolean onFork(StructuredTaskScope$Subtask<T>);
public default boolean onComplete(StructuredTaskScope$Subtask<T>);
public abstract R result() throws R_X;
public abstract R timeout() throws R_X;
public static <T, R_X extends Throwable> StructuredTaskScope$Joiner<T, List<T>, R_X> allSuccessfulOrThrow(Function<Throwable, R_X>);
public static <T> StructuredTaskScope$Joiner<T, List<T>, ExecutionException> allSuccessfulOrThrow();
public static <T, R_X extends Throwable> StructuredTaskScope$Joiner<T, T, R_X> anySuccessfulOrThrow(Function<Throwable, R_X>);
public static <T> StructuredTaskScope$Joiner<T, T, ExecutionException> anySuccessfulOrThrow();
public static <T, R_X extends Throwable> StructuredTaskScope$Joiner<T, Void, R_X> awaitAllSuccessfulOrThrow(Function<Throwable, R_X>);
public static <T> StructuredTaskScope$Joiner<T, Void, ExecutionException> awaitAllSuccessfulOrThrow();
public static <T> StructuredTaskScope$Joiner<T, List<StructuredTaskScope$Subtask<T>>, RuntimeException> allUntil(Predicate<? super StructuredTaskScope$Subtask<T>>);
}
Compiled from "StructuredTaskScope.java"
public interface StructuredTaskScope$Configuration {
public abstract StructuredTaskScope$Configuration withThreadFactory(ThreadFactory);
public abstract StructuredTaskScope$Configuration withName(String);
public abstract StructuredTaskScope$Configuration withTimeout(java.time.Duration);
}
Compiled from "StructuredTaskScope.java"
public interface StructuredTaskScope$Subtask<T> extends Supplier<T> {
public abstract StructuredTaskScope$Subtask$State state();
public abstract T get();
public abstract Throwable exception();
}
Compiled from "StructuredTaskScope.java"
public final class StructuredTaskScope$Subtask$State extends Enum<StructuredTaskScope$Subtask$State> {
public static final StructuredTaskScope$Subtask$State UNAVAILABLE;
public static final StructuredTaskScope$Subtask$State SUCCESS;
public static final StructuredTaskScope$Subtask$State FAILED;
public static StructuredTaskScope$Subtask$State[] values();
public static StructuredTaskScope$Subtask$State valueOf(String);
static {};
}
@@ -0,0 +1,74 @@
$ diff api-25 api-26 (signature lines only; the first line of each file names the build)
< public static <T, R> StructuredTaskScope<T, R> open(StructuredTaskScope$Joiner<? super T, ? extends R>, Function<StructuredTaskScope$Configuration, StructuredTaskScope$Configuration>);
> public static <T, R> StructuredTaskScope<T, R> open(StructuredTaskScope$Joiner<? super T, ? extends R>, UnaryOperator<StructuredTaskScope$Configuration>);
< public default boolean onFork(StructuredTaskScope$Subtask<? extends T>);
< public default boolean onComplete(StructuredTaskScope$Subtask<? extends T>);
> public default boolean onFork(StructuredTaskScope$Subtask<T>);
> public default boolean onComplete(StructuredTaskScope$Subtask<T>);
> public default void onTimeout();
< public static <T> StructuredTaskScope$Joiner<T, stream.Stream<StructuredTaskScope$Subtask<T>>> allSuccessfulOrThrow();
< public static <T> StructuredTaskScope$Joiner<T, T> anySuccessfulResultOrThrow();
> public static <T> StructuredTaskScope$Joiner<T, List<T>> allSuccessfulOrThrow();
> public static <T> StructuredTaskScope$Joiner<T, T> anySuccessfulOrThrow();
< public static <T> StructuredTaskScope$Joiner<T, stream.Stream<StructuredTaskScope$Subtask<T>>> allUntil(Predicate<StructuredTaskScope$Subtask<? extends T>>);
> public static <T> StructuredTaskScope$Joiner<T, List<StructuredTaskScope$Subtask<T>>> allUntil(Predicate<StructuredTaskScope$Subtask<T>>);
$ diff api-26 api-27 (signature lines only; the first line of each file names the build)
< public interface StructuredTaskScope<T, R> extends AutoCloseable {
< public static <T, R> StructuredTaskScope<T, R> open(StructuredTaskScope$Joiner<? super T, ? extends R>, UnaryOperator<StructuredTaskScope$Configuration>);
< public static <T, R> StructuredTaskScope<T, R> open(StructuredTaskScope$Joiner<? super T, ? extends R>);
< public static <T> StructuredTaskScope<T, Void> open();
> public interface StructuredTaskScope<T, R, R_X extends Throwable> extends AutoCloseable {
> public static <T, R, R_X extends Throwable> StructuredTaskScope<T, R, R_X> open(StructuredTaskScope$Joiner<? super T, ? extends R, R_X>, UnaryOperator<StructuredTaskScope$Configuration>);
> public static <T, R, R_X extends Throwable> StructuredTaskScope<T, R, R_X> open(StructuredTaskScope$Joiner<? super T, ? extends R, R_X>);
> public static <T> StructuredTaskScope<T, Void, ExecutionException> open(UnaryOperator<StructuredTaskScope$Configuration>);
> public static <T> StructuredTaskScope<T, Void, ExecutionException> open();
< public abstract R join() throws InterruptedException;
> public abstract R join() throws R_X, InterruptedException;
< public interface StructuredTaskScope$Joiner<T, R> {
> public interface StructuredTaskScope$Joiner<T, R, R_X extends Throwable> {
< public default void onTimeout();
< public abstract R result() throws Throwable;
< public static <T> StructuredTaskScope$Joiner<T, List<T>> allSuccessfulOrThrow();
< public static <T> StructuredTaskScope$Joiner<T, T> anySuccessfulOrThrow();
< public static <T> StructuredTaskScope$Joiner<T, Void> awaitAllSuccessfulOrThrow();
< public static <T> StructuredTaskScope$Joiner<T, Void> awaitAll();
< public static <T> StructuredTaskScope$Joiner<T, List<StructuredTaskScope$Subtask<T>>> allUntil(Predicate<StructuredTaskScope$Subtask<T>>);
> public abstract R result() throws R_X;
> public abstract R timeout() throws R_X;
> public static <T, R_X extends Throwable> StructuredTaskScope$Joiner<T, List<T>, R_X> allSuccessfulOrThrow(Function<Throwable, R_X>);
> public static <T> StructuredTaskScope$Joiner<T, List<T>, ExecutionException> allSuccessfulOrThrow();
> public static <T, R_X extends Throwable> StructuredTaskScope$Joiner<T, T, R_X> anySuccessfulOrThrow(Function<Throwable, R_X>);
> public static <T> StructuredTaskScope$Joiner<T, T, ExecutionException> anySuccessfulOrThrow();
> public static <T, R_X extends Throwable> StructuredTaskScope$Joiner<T, Void, R_X> awaitAllSuccessfulOrThrow(Function<Throwable, R_X>);
> public static <T> StructuredTaskScope$Joiner<T, Void, ExecutionException> awaitAllSuccessfulOrThrow();
> public static <T> StructuredTaskScope$Joiner<T, List<StructuredTaskScope$Subtask<T>>, RuntimeException> allUntil(Predicate<? super StructuredTaskScope$Subtask<T>>);
$ diff api-27 api-28 (signature lines only; the first line of each file names the build)
(no difference)
$ jimage list modules | grep -E 'FailedException|TimeoutException|CancelledByTimeout' (25.0.5+8-LTS)
java/util/concurrent/StructuredTaskScope$FailedException.class
java/util/concurrent/StructuredTaskScope$TimeoutException.class
$ jimage list modules | grep -E 'FailedException|TimeoutException|CancelledByTimeout' (26.0.2.1+1)
java/util/concurrent/StructuredTaskScope$FailedException.class
java/util/concurrent/StructuredTaskScope$TimeoutException.class
$ jimage list modules | grep -E 'FailedException|TimeoutException|CancelledByTimeout' (27.0.1.0.2+1)
java/util/concurrent/StructuredTaskScope$CancelledByTimeoutException.class
$ jimage list modules | grep -E 'FailedException|TimeoutException|CancelledByTimeout' (28-beta+21-ea)
java/util/concurrent/StructuredTaskScope$CancelledByTimeoutException.class
$ javap -v StructuredTaskScope | grep 'PreviewFeature(' -A1 (26.0.2.1+1)
jdk.internal.javac.PreviewFeature(
feature=Ljdk/internal/javac/PreviewFeature$Feature;.STRUCTURED_CONCURRENCY
$ javap -v StructuredTaskScope | grep 'PreviewFeature(' -A1 (27.0.1.0.2+1)
jdk.internal.javac.PreviewFeature(
feature=Ljdk/internal/javac/PreviewFeature$Feature;.STRUCTURED_CONCURRENCY
$ javap -v StructuredTaskScope | grep 'PreviewFeature(' -A1 (28-beta+21-ea)
(no @PreviewFeature annotation: the API is final)
@@ -0,0 +1,83 @@
$ javac --enable-preview --release 26 broken/MissingCatch.java (26.0.2.1+1)
exit=0
$ javac broken/MissingCatch.java (28-beta+21-ea)
broken/MissingCatch.java:8: error: unreported exception ExecutionException; must be caught or declared to be thrown
scope.join();
^
1 error
exit=1
$ javac --enable-preview --release 26 broken/OldFailedException.java (26.0.2.1+1)
exit=0
$ javac broken/OldFailedException.java (28-beta+21-ea)
broken/OldFailedException.java:9: error: cannot find symbol
} catch (StructuredTaskScope.FailedException e) {
^
symbol: class FailedException
location: interface StructuredTaskScope
1 error
exit=1
$ javac --enable-preview --release 26 broken/OldTimeoutException.java (26.0.2.1+1)
exit=0
$ javac broken/OldTimeoutException.java (28-beta+21-ea)
broken/OldTimeoutException.java:11: error: cannot find symbol
} catch (StructuredTaskScope.TimeoutException e) {
^
symbol: class TimeoutException
location: interface StructuredTaskScope
1 error
exit=1
$ javac --enable-preview --release 26 broken/AwaitAll.java (26.0.2.1+1)
exit=0
$ javac broken/AwaitAll.java (28-beta+21-ea)
broken/AwaitAll.java:7: error: cannot find symbol
try (var scope = StructuredTaskScope.open(Joiner.<String>awaitAll())) {
^
symbol: method <String>awaitAll()
location: interface Joiner
1 error
exit=1
$ javac --enable-preview --release 26 broken/OldJoiner.java (26.0.2.1+1)
exit=0
$ javac broken/OldJoiner.java (28-beta+21-ea)
broken/OldJoiner.java:7: error: wrong number of type arguments; required 3
public class OldJoiner<T> implements Joiner<T, List<T>> {
^
1 error
exit=1
$ javac --enable-preview --release 26 broken/ShutdownOnFailure.java (26.0.2.1+1)
broken/ShutdownOnFailure.java:6: error: cannot find symbol
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
^
symbol: class ShutdownOnFailure
location: interface StructuredTaskScope
1 error
exit=1
$ javac broken/ShutdownOnFailure.java (28-beta+21-ea)
broken/ShutdownOnFailure.java:6: error: cannot find symbol
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
^
symbol: class ShutdownOnFailure
location: interface StructuredTaskScope
1 error
exit=1
# the same sources on JDK 25 for ShutdownOnFailure
$ javac --enable-preview --release 25 broken/ShutdownOnFailure.java (25.0.5+8-LTS)
broken/ShutdownOnFailure.java:6: error: cannot find symbol
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
^
symbol: class ShutdownOnFailure
location: interface StructuredTaskScope
1 error
exit=1
@@ -0,0 +1,59 @@
# compile on 27 with preview, then run the class file on 27 and on 28
$ java --enable-preview Server (27.0.1.0.2+1)
Response[user=Alice, order=42]
Runtime: 27.0.1.0.2+1
$ java --enable-preview Server (28-beta+21-ea)
Error: LinkageError occurred while loading main class Server
java.lang.UnsupportedClassVersionError: Server (class file version 71.65535) was compiled with preview features that are unsupported. This version of the Java Runtime only recognizes preview features for class file version 72.65535
$ java Server (27.0.1.0.2+1)
Error: LinkageError occurred while loading main class Server
java.lang.UnsupportedClassVersionError: Preview features are not enabled for Server (class file version 71.65535). Try running with '--enable-preview'
# the class file's minor version
minor version: 65535
major version: 71
# javac 28 asked for --release 27
$ javac --release 27 src/Server.java (28-beta+21-ea)
src/Server.java:2: error: StructuredTaskScope is a preview API and is disabled by default.
import java.util.concurrent.StructuredTaskScope;
^
(use --enable-preview to enable preview APIs)
src/Server.java:3: error: StructuredTaskScope is a preview API and is disabled by default.
import java.util.concurrent.StructuredTaskScope.Subtask;
^
(use --enable-preview to enable preview APIs)
src/Server.java:3: error: Subtask is a preview API and is disabled by default.
import java.util.concurrent.StructuredTaskScope.Subtask;
^
(use --enable-preview to enable preview APIs)
src/Server.java:22: error: StructuredTaskScope is a preview API and is disabled by default.
try (var scope = StructuredTaskScope.open()) { // 1. open
^
(use --enable-preview to enable preview APIs)
src/Server.java:23: error: Subtask is a preview API and is disabled by default.
Subtask<String> user = scope.fork(this::findUser); // 2. fork
^
(use --enable-preview to enable preview APIs)
src/Server.java:24: error: Subtask is a preview API and is disabled by default.
Subtask<Integer> order = scope.fork(this::fetchOrder); // fork
^
(use --enable-preview to enable preview APIs)
6 errors
# javac 28 with --enable-preview --release 28 on the same file (nothing in it needs preview)
$ javac --enable-preview --release 28 src/Server.java (28-beta+21-ea)
Note: src/Server.java uses preview features of Java SE 28.
Note: Recompile with -Xlint:preview for details.
exit=0
# where that Note comes from: the first lines of -Xlint:preview
$ javac --enable-preview --release 28 -Xlint:preview src/Server.java 2>&1 | head -3 (28-beta+21-ea)
warning: [preview] class file for /@/java.base/java/lang/Record.sig uses preview features of Java SE 28.
warning: [preview] class file for /@/java.base/java/time/Duration.sig uses preview features of Java SE 28.
warning: [preview] class file for /@/java.base/java/lang/Integer.sig uses preview features of Java SE 28.
# recompile on 28 without any flag and run on 28
$ javac src/Server.java && java Server (28-beta+21-ea)
Response[user=Alice, order=42]
Runtime: 28-beta+21-ea
@@ -0,0 +1,9 @@
$ javac src/Snippets.java && java Snippets (28-beta+21-ea)
Response[user=Alice, order=42]
Response[user=Alice, order=42]
[1, 2]
a
Response[user=Alice, order=42]
$ javac src/QuickReference.java && java QuickReference (28-beta+21-ea)
quick reference compiled and ran
@@ -0,0 +1,20 @@
$ java RandomTasks 5 (28-beta+21-ea)
Duration: 487
Duration: 92
Duration: 474
Duration: 424
Duration: 506
Result: 487
Result: 92
Result: 474
Result: 424
Result: 506
$ java RandomTasks 3 (28-beta+21-ea)
Duration: 734
Duration: 660
Duration: 210
Duration: 581
Duration: 128
ExecutionException: TooSlowException: Duration 734 greater than threshold 700
@@ -0,0 +1,3 @@
$ javac src/StuckClose.java && java StuckClose (28-beta+21-ea)
subtask that responds to interrupts: caught fails at 50 ms; the try block took >= 1400 ms: false
subtask that ignores interrupts : caught fails at 50 ms; the try block took >= 1400 ms: true
@@ -0,0 +1,23 @@
$ jcmd <pid> Thread.dump_to_file -format=json threads.json (28-beta+21-ea)
<pid>:
Created <tmp>/threads.json
# the scope "orders" and its child scope in threads.json (stack frames, timestamps and ids trimmed or masked)
"container": "orders\/jdk.internal.misc.ThreadFlock$ThreadContainerImpl@<hash>",
"parent": "<root>",
"owner": <n>,
"tid": <n>,
"virtual": true,
"state": "TIMED_WAITING",
"ThreadDump.lambda$main$1(ThreadDump.java:12)",
"tid": <n>,
"virtual": true,
"state": "WAITING",
"ThreadDump.lambda$main$2(ThreadDump.java:16)",
"container": "inventory\/jdk.internal.misc.ThreadFlock$ThreadContainerImpl@<hash>",
"parent": "orders\/jdk.internal.misc.ThreadFlock$ThreadContainerImpl@<hash>",
"owner": <n>,
"tid": <n>,
"virtual": true,
"state": "TIMED_WAITING",
"ThreadDump.lambda$main$4(ThreadDump.java:15)",