diff --git a/make/conf/docs-modules.conf b/make/conf/docs-modules.conf
index e009deb85b3..09b26eae9a1 100644
--- a/make/conf/docs-modules.conf
+++ b/make/conf/docs-modules.conf
@@ -42,7 +42,6 @@ DOCS_MODULES= \
jdk.hotspot.agent \
jdk.httpserver \
jdk.jpackage \
- jdk.incubator.concurrent \
jdk.incubator.vector \
jdk.jartool \
jdk.javadoc \
diff --git a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf
index 186fd2b906e..2c95b8570fd 100644
--- a/make/conf/module-loader-map.conf
+++ b/make/conf/module-loader-map.conf
@@ -43,7 +43,6 @@ BOOT_MODULES= \
java.rmi \
java.security.sasl \
java.xml \
- jdk.incubator.concurrent \
jdk.incubator.vector \
jdk.internal.vm.ci \
jdk.jfr \
diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp
index df5ea30484b..b344d5ed932 100644
--- a/src/hotspot/share/classfile/vmSymbols.hpp
+++ b/src/hotspot/share/classfile/vmSymbols.hpp
@@ -118,6 +118,8 @@
template(java_lang_StringBuilder, "java/lang/StringBuilder") \
template(java_lang_CharSequence, "java/lang/CharSequence") \
template(java_lang_SecurityManager, "java/lang/SecurityManager") \
+ template(java_lang_ScopedValue, "java/lang/ScopedValue") \
+ template(java_lang_ScopedValue_Carrier, "java/lang/ScopedValue$Carrier") \
template(java_security_AccessControlContext, "java/security/AccessControlContext") \
template(java_security_AccessController, "java/security/AccessController") \
template(executePrivileged_name, "executePrivileged") \
@@ -157,8 +159,6 @@
template(jdk_internal_loader_BuiltinClassLoader, "jdk/internal/loader/BuiltinClassLoader") \
template(jdk_internal_loader_ClassLoaders_AppClassLoader, "jdk/internal/loader/ClassLoaders$AppClassLoader") \
template(jdk_internal_loader_ClassLoaders_PlatformClassLoader, "jdk/internal/loader/ClassLoaders$PlatformClassLoader") \
- template(jdk_incubator_concurrent_ScopedValue, "jdk/incubator/concurrent/ScopedValue") \
- template(jdk_incubator_concurrent_ScopedValue_Carrier, "jdk/incubator/concurrent/ScopedValue$Carrier") \
\
/* Java runtime version access */ \
template(java_lang_VersionProps, "java/lang/VersionProps") \
diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp
index 2d3b66c8fd9..9f5327eb313 100644
--- a/src/hotspot/share/prims/jvm.cpp
+++ b/src/hotspot/share/prims/jvm.cpp
@@ -1362,7 +1362,7 @@ class ScopedValueBindingsResolver {
public:
InstanceKlass* Carrier_klass;
ScopedValueBindingsResolver(JavaThread* THREAD) {
- Klass *k = SystemDictionary::resolve_or_fail(vmSymbols::jdk_incubator_concurrent_ScopedValue_Carrier(), true, THREAD);
+ Klass *k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_ScopedValue_Carrier(), true, THREAD);
Carrier_klass = InstanceKlass::cast(k);
}
};
@@ -1395,7 +1395,7 @@ JVM_ENTRY(jobject, JVM_FindScopedValueBindings(JNIEnv *env, jclass cls))
if (loc != -1) {
javaVFrame *frame = vfst.asJavaVFrame();
StackValueCollection* locals = frame->locals();
- StackValue* head_sv = locals->at(loc); // jdk/incubator/concurrent/ScopedValue$Snapshot
+ StackValue* head_sv = locals->at(loc); // java/lang/ScopedValue$Snapshot
Handle result = head_sv->get_obj();
assert(!head_sv->obj_is_scalar_replaced(), "found scalar-replaced object");
if (result() != nullptr) {
diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/ScopedValue.java b/src/java.base/share/classes/java/lang/ScopedValue.java
similarity index 70%
rename from src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/ScopedValue.java
rename to src/java.base/share/classes/java/lang/ScopedValue.java
index 882bc8eb219..58083b9680c 100644
--- a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/ScopedValue.java
+++ b/src/java.base/share/classes/java/lang/ScopedValue.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2022, Red Hat Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -24,109 +24,141 @@
* questions.
*/
-package jdk.incubator.concurrent;
+package java.lang;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.lang.ref.Reference;
import java.util.concurrent.Callable;
+import java.util.concurrent.StructuredTaskScope;
+import java.util.concurrent.StructureViolationException;
import java.util.function.Supplier;
-import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaUtilConcurrentTLRAccess;
import jdk.internal.access.SharedSecrets;
+import jdk.internal.javac.PreviewFeature;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden;
-import jdk.internal.vm.annotation.Stable;
import jdk.internal.vm.ScopedValueContainer;
import sun.security.action.GetPropertyAction;
/**
- * A value that is set once and is then available for reading for a bounded period of
- * execution by a thread. A {@code ScopedValue} allows for safely and efficiently sharing
- * data for a bounded period of execution without passing the data as method arguments.
+ * A value that may be safely and efficiently shared to methods without using method
+ * parameters.
*
- *
{@code ScopedValue} defines the {@link #where(ScopedValue, Object, Runnable)}
- * method to set the value of a {@code ScopedValue} for the bouned period of execution by
- * a thread of the runnable's {@link Runnable#run() run} method. The unfolding execution of
- * the methods executed by {@code run} defines a dynamic scope . The scoped
- * value is {@linkplain #isBound() bound} while executing in the dynamic scope, it reverts
- * to being unbound when the {@code run} method completes (normally or with an
- * exception). Code executing in the dynamic scope uses the {@code ScopedValue} {@link
- * #get() get} method to read its value.
+ *
In the Java programming language, data is usually passed to a method by means of a
+ * method parameter. The data may need to be passed through a sequence of many methods to
+ * get to the method that makes use of the data. Every method in the sequence of calls
+ * needs to declare the parameter and every method has access to the data.
+ * {@code ScopedValue} provides a means to pass data to a faraway method (typically a
+ * callback ) without using method parameters. In effect, a {@code ScopedValue}
+ * is an implicit method parameter . It is "as if" every method in a sequence of
+ * calls has an additional parameter. None of the methods declare the parameter and only
+ * the methods that have access to the {@code ScopedValue} object can access its value
+ * (the data). {@code ScopedValue} makes it possible to securely pass data from a
+ * caller to a faraway callee through a sequence of intermediate methods
+ * that do not declare a parameter for the data and have no access to the data.
*
- *
Like a {@linkplain ThreadLocal thread-local variable}, a scoped value has multiple
- * incarnations, one per thread. The particular incarnation that is used depends on which
- * thread calls its methods.
+ *
The {@code ScopedValue} API works by executing a method with a {@code ScopedValue}
+ * object bound to some value for the bounded period of execution of a method.
+ * The method may invoke another method, which in turn may invoke another. The unfolding
+ * execution of the methods define a dynamic scope . Code in these methods with
+ * access to the {@code ScopedValue} object may read its value. The {@code ScopedValue}
+ * object reverts to being unbound when the original method completes normally or
+ * with an exception. The {@code ScopedValue} API supports executing a {@link Runnable#run()
+ * Runnable.run}, {@link Callable#call() Callable.call}, or {@link Supplier#get() Supplier.get}
+ * method with a {@code ScopedValue} bound to a value.
*
- *
Consider the following example with a scoped value {@code USERNAME} that is
- * bound to the value "{@code duke}" for the execution, by a thread, of a run
- * method that invokes {@code doSomething()}.
+ *
Consider the following example with a scoped value "{@code NAME}" bound to the value
+ * "{@code duke}" for the execution of a {@code run} method. The {@code run} method, in
+ * turn, invokes {@code doSomething}.
* {@snippet lang=java :
* // @link substring="newInstance" target="#newInstance" :
- * private static final ScopedValue USERNAME = ScopedValue.newInstance();
+ * private static final ScopedValue NAME = ScopedValue.newInstance();
*
- * ScopedValue.where(USERNAME, "duke", () -> doSomething());
+ * // @link substring="runWhere" target="#runWhere" :
+ * ScopedValue.runWhere(NAME, "duke", () -> doSomething());
* }
- * Code executed directly or indirectly by {@code doSomething()} that invokes {@code
- * USERNAME.get()} will read the value "{@code duke}". The scoped value is bound while
- * executing {@code doSomething()} and becomes unbound when {@code doSomething()}
- * completes (normally or with an exception). If one thread were to call {@code
- * doSomething()} with {@code USERNAME} bound to "{@code duke1}", and another thread
- * were to call the method with {@code USERNAME} bound to "{@code duke2}", then
- * {@code USERNAME.get()} would read the value "{@code duke1}" or "{@code duke2}",
- * depending on which thread is executing.
+ * Code executed directly or indirectly by {@code doSomething}, with access to the field
+ * {@code NAME}, can invoke {@code NAME.get()} to read the value "{@code duke}". {@code
+ * NAME} is bound while executing the {@code run} method. It reverts to being unbound when
+ * the {@code run} method completes.
*
- * In addition to the {@code where} method that executes a {@code run} method, {@code
- * ScopedValue} defines the {@link #where(ScopedValue, Object, Callable)} method to execute
- * a method that returns a result. It also defines the {@link #where(ScopedValue, Object)}
- * method for cases where it is useful to accumulate mappings of {@code ScopedValue} to
- * value.
+ *
The example using {@code runWhere} invokes a method that does not return a result.
+ * The {@link #callWhere(ScopedValue, Object, Callable) callWhere} and {@link
+ * #getWhere(ScopedValue, Object, Supplier) getWhere} can be used to invoke a method that
+ * returns a result.
+ * In addition, {@code ScopedValue} defines the {@link #where(ScopedValue, Object)} method
+ * for cases where multiple mappings (of {@code ScopedValue} to value) are accumulated
+ * in advance of calling a method with all {@code ScopedValue}s bound to their value.
*
- *
A {@code ScopedValue} will typically be declared in a {@code final} and {@code
- * static} field. The accessibility of the field will determine which components can
- * bind or read its value.
+ *
Bindings are per-thread
*
- * Unless otherwise specified, passing a {@code null} argument to a method in this
- * class will cause a {@link NullPointerException} to be thrown.
+ * A {@code ScopedValue} binding to a value is per-thread. Invoking {@code xxxWhere}
+ * executes a method with a {@code ScopedValue} bound to a value for the current thread.
+ * The {@link #get() get} method returns the value bound for the current thread.
+ *
+ *
In the example, if code executed by one thread invokes this:
+ * {@snippet lang=java :
+ * ScopedValue.runWhere(NAME, "duke1", () -> doSomething());
+ * }
+ * and code executed by another thread invokes:
+ * {@snippet lang=java :
+ * ScopedValue.runWhere(NAME, "duke2", () -> doSomething());
+ * }
+ * then code in {@code doSomething} (or any method that it calls) invoking {@code NAME.get()}
+ * will read the value "{@code duke1}" or "{@code duke2}", depending on which thread is
+ * executing.
+ *
+ *
Scoped values as capabilities
+ *
+ * A {@code ScopedValue} object should be treated as a capability or a key to
+ * access its value when the {@code ScopedValue} is bound. Secure usage depends on access
+ * control (see The Java Virtual Machine Specification , Section {@jvms 5.4.4})
+ * and taking care to not share the {@code ScopedValue} object. In many cases, a {@code
+ * ScopedValue} will be declared in a {@code final} and {@code static} field so that it
+ * is only accessible to code in a single class (or nest).
*
*
*
* The {@code ScopedValue} API allows a new binding to be established for nested
* dynamic scopes . This is known as rebinding . A {@code ScopedValue} that
- * is bound to some value may be bound to a new value for the bounded execution of some
+ * is bound to a value may be bound to a new value for the bounded execution of a new
* method. The unfolding execution of code executed by that method defines the nested
- * dynamic scope. When the method completes (normally or with an exception), the value of
- * the {@code ScopedValue} reverts to its previous value.
+ * dynamic scope. When the method completes, the value of the {@code ScopedValue} reverts
+ * to its previous value.
*
- * In the above example, suppose that code executed by {@code doSomething()} binds
- * {@code USERNAME} to a new value with:
+ *
In the above example, suppose that code executed by {@code doSomething} binds
+ * {@code NAME} to a new value with:
* {@snippet lang=java :
- * ScopedValue.where(USERNAME, "duchess", () -> doMore());
+ * ScopedValue.runWhere(NAME, "duchess", () -> doMore());
* }
* Code executed directly or indirectly by {@code doMore()} that invokes {@code
- * USERNAME.get()} will read the value "{@code duchess}". When {@code doMore()} completes
- * (normally or with an exception), the value of {@code USERNAME} reverts to
- * "{@code duke}".
+ * NAME.get()} will read the value "{@code duchess}". When {@code doMore()} completes
+ * then the value of {@code NAME} reverts to "{@code duke}".
*
*
*
- * {@code ScopedValue} supports sharing data across threads. This sharing is limited to
+ * {@code ScopedValue} supports sharing across threads. This sharing is limited to
* structured cases where child threads are started and terminate within the bounded
- * period of execution by a parent thread. More specifically, when using a {@link
- * StructuredTaskScope}, scoped value bindings are captured when creating a
- * {@code StructuredTaskScope} and inherited by all threads started in that scope with
- * the {@link StructuredTaskScope#fork(Callable) fork} method.
+ * period of execution by a parent thread. When using a {@link StructuredTaskScope},
+ * scoped value bindings are captured when creating a {@code StructuredTaskScope}
+ * and inherited by all threads started in that task scope with the
+ * {@link StructuredTaskScope#fork(Callable) fork} method.
*
- * In the following example, the {@code ScopedValue} {@code USERNAME} is bound to the
+ *
A {@code ScopedValue} that is shared across threads requires that the value be an
+ * immutable object or for all access to the value to be appropriately synchronized.
+ *
+ *
In the following example, the {@code ScopedValue} {@code NAME} is bound to the
* value "{@code duke}" for the execution of a runnable operation. The code in the {@code
- * run} method creates a {@code StructuredTaskScope} and forks three child threads. Code
- * executed directly or indirectly by these threads running {@code childTask1()},
- * {@code childTask2()}, and {@code childTask3()} will read the value "{@code duke}".
+ * run} method creates a {@code StructuredTaskScope} that forks three tasks. Code executed
+ * directly or indirectly by these threads running {@code childTask1()}, {@code childTask2()},
+ * and {@code childTask3()} that invokes {@code NAME.get()} will read the value
+ * "{@code duke}".
*
* {@snippet lang=java :
- * private static final ScopedValue USERNAME = ScopedValue.newInstance();
+ * private static final ScopedValue NAME = ScopedValue.newInstance();
- * ScopedValue.where(USERNAME, "duke", () -> {
+ * ScopedValue.runWhere(NAME, "duke", () -> {
* try (var scope = new StructuredTaskScope()) {
*
* scope.fork(() -> childTask1());
@@ -138,6 +170,23 @@ import sun.security.action.GetPropertyAction;
* });
* }
*
+ * Unless otherwise specified, passing a {@code null} argument to a method in this
+ * class will cause a {@link NullPointerException} to be thrown.
+ *
+ * @apiNote
+ * A {@code ScopedValue} should be preferred over a {@link ThreadLocal} for cases where
+ * the goal is "one-way transmission" of data without using method parameters. While a
+ * {@code ThreadLocal} can be used to pass data to a method without using method parameters,
+ * it does suffer from a number of issues:
+ *
+ * {@code ThreadLocal} does not prevent code in a faraway callee from {@linkplain
+ * ThreadLocal#set(Object) setting} a new value.
+ * A {@code ThreadLocal} has an unbounded lifetime and thus continues to have a value
+ * after a method completes, unless explicitly {@linkplain ThreadLocal#remove() removed}.
+ * {@linkplain InheritableThreadLocal Inheritance} is expensive - the map of
+ * thread-locals to values must be copied when creating each child thread.
+ *
+ *
* @implNote
* Scoped values are designed to be used in fairly small
* numbers. {@link #get} initially performs a search through enclosing
@@ -158,11 +207,11 @@ import sun.security.action.GetPropertyAction;
* it makes sense to create a record class to hold those values, and
* then bind a single {@code ScopedValue} to an instance of that record.
*
- * For this incubator release, the reference implementation
+ *
For this release, the reference implementation
* provides some system properties to tune the performance of scoped
* values.
*
- *
The system property {@code jdk.incubator.concurrent.ScopedValue.cacheSize}
+ *
The system property {@code java.lang.ScopedValue.cacheSize}
* controls the size of the (per-thread) scoped-value cache. This cache is crucial
* for the performance of scoped values. If it is too small,
* the runtime library will repeatedly need to scan for each
@@ -171,7 +220,7 @@ import sun.security.action.GetPropertyAction;
* be varied from 2 to 16 entries in size. {@code ScopedValue.cacheSize}
* must be an integer power of 2.
*
- *
For example, you could use {@code -Djdk.incubator.concurrent.ScopedValue.cacheSize=8}.
+ *
For example, you could use {@code -Djava.lang.ScopedValue.cacheSize=8}.
*
*
The other system property is {@code jdk.preserveScopedValueCache}.
* This property determines whether the per-thread scoped-value
@@ -184,13 +233,12 @@ import sun.security.action.GetPropertyAction;
* memory saving, but each virtual thread's scoped-value cache would
* have to be regenerated after a blocking operation.
*
- * @param the type of the object bound to this {@code ScopedValue}
- * @since 20
+ * @param the type of the value
+ * @since 21
*/
+@PreviewFeature(feature = PreviewFeature.Feature.SCOPED_VALUES)
public final class ScopedValue {
- private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
-
- private final @Stable int hash;
+ private final int hash;
@Override
public int hashCode() { return hash; }
@@ -243,7 +291,7 @@ public final class ScopedValue {
/**
* A mapping of scoped values, as keys , to values.
*
- * A {@code Carrier} is used to accumlate mappings so that an operation (a
+ *
A {@code Carrier} is used to accumulate mappings so that an operation (a
* {@link Runnable} or {@link Callable}) can be executed with all scoped values in the
* mapping bound to values. The following example runs an operation with {@code k1}
* bound (or rebound) to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
@@ -259,8 +307,9 @@ public final class ScopedValue {
* Unless otherwise specified, passing a {@code null} argument to a method in
* this class will cause a {@link NullPointerException} to be thrown.
*
- * @since 20
+ * @since 21
*/
+ @PreviewFeature(feature = PreviewFeature.Feature.SCOPED_VALUES)
public static final class Carrier {
// Bit masks: a 1 in postion n indicates that this set of bound values
// hits that slot in the cache.
@@ -283,8 +332,7 @@ public final class ScopedValue {
/**
* Add a binding to this map, returning a new Carrier instance.
*/
- private static final Carrier where(ScopedValue key, T value,
- Carrier prev) {
+ private static Carrier where(ScopedValue key, T value, Carrier prev) {
return new Carrier(key, value, prev);
}
@@ -311,11 +359,11 @@ public final class ScopedValue {
return where(key, value, null);
}
- final Object get() {
+ Object get() {
return value;
}
- final ScopedValue> getKey() {
+ ScopedValue> getKey() {
return key;
}
@@ -360,7 +408,7 @@ public final class ScopedValue {
* @param the type of the result of the operation
* @return the result
* @throws Exception if {@code op} completes with an exception
- * @see ScopedValue#where(ScopedValue, Object, Callable)
+ * @see ScopedValue#callWhere(ScopedValue, Object, Callable)
*/
public R call(Callable extends R> op) throws Exception {
Objects.requireNonNull(op);
@@ -370,6 +418,48 @@ public final class ScopedValue {
return runWith(newSnapshot, op);
}
+ /**
+ * Invokes a supplier of results with each scoped value in this mapping bound
+ * to its value in the current thread.
+ * When the operation completes (normally or with an exception), each scoped value
+ * in the mapping will revert to being unbound, or revert to its previous value
+ * when previously bound, in the current thread.
+ *
+ * Scoped values are intended to be used in a structured manner .
+ * If {@code op} creates a {@link StructuredTaskScope} but does not {@linkplain
+ * StructuredTaskScope#close() close} it, then exiting {@code op} causes the
+ * underlying construct of each {@code StructuredTaskScope} created in the
+ * dynamic scope to be closed. This may require blocking until all child threads
+ * have completed their sub-tasks. The closing is done in the reverse order that
+ * they were created. Once closed, {@link StructureViolationException} is thrown.
+ *
+ * @param op the operation to run
+ * @param the type of the result of the operation
+ * @return the result
+ * @see ScopedValue#getWhere(ScopedValue, Object, Supplier)
+ */
+ public R get(Supplier extends R> op) {
+ Objects.requireNonNull(op);
+ Cache.invalidate(bitmask);
+ var prevSnapshot = scopedValueBindings();
+ var newSnapshot = new Snapshot(this, prevSnapshot);
+ return runWith(newSnapshot, new CallableAdapter(op));
+ }
+
+ // A lightweight adapter from Supplier to Callable. This is
+ // used here to create the Callable which is passed to
+ // Carrier#call() in this thread because it needs neither
+ // runtime bytecode generation nor any release fencing.
+ private static final class CallableAdapter implements Callable {
+ private /*non-final*/ Supplier extends V> s;
+ CallableAdapter(Supplier extends V> s) {
+ this.s = s;
+ }
+ public V call() {
+ return s.get();
+ }
+ }
+
/**
* Execute the action with a set of ScopedValue bindings.
*
@@ -379,14 +469,14 @@ public final class ScopedValue {
*/
@Hidden
@ForceInline
- private R runWith(Snapshot newSnapshot, Callable op) throws Exception {
+ private R runWith(Snapshot newSnapshot, Callable op) {
try {
- JLA.setScopedValueBindings(newSnapshot);
- JLA.ensureMaterializedForStackWalk(newSnapshot);
+ Thread.setScopedValueBindings(newSnapshot);
+ Thread.ensureMaterializedForStackWalk(newSnapshot);
return ScopedValueContainer.call(op);
} finally {
Reference.reachabilityFence(newSnapshot);
- JLA.setScopedValueBindings(newSnapshot.prev);
+ Thread.setScopedValueBindings(newSnapshot.prev);
Cache.invalidate(bitmask);
}
}
@@ -407,7 +497,7 @@ public final class ScopedValue {
* they were created. Once closed, {@link StructureViolationException} is thrown.
*
* @param op the operation to run
- * @see ScopedValue#where(ScopedValue, Object, Runnable)
+ * @see ScopedValue#runWhere(ScopedValue, Object, Runnable)
*/
public void run(Runnable op) {
Objects.requireNonNull(op);
@@ -428,12 +518,12 @@ public final class ScopedValue {
@ForceInline
private void runWith(Snapshot newSnapshot, Runnable op) {
try {
- JLA.setScopedValueBindings(newSnapshot);
- JLA.ensureMaterializedForStackWalk(newSnapshot);
+ Thread.setScopedValueBindings(newSnapshot);
+ Thread.ensureMaterializedForStackWalk(newSnapshot);
ScopedValueContainer.run(op);
} finally {
Reference.reachabilityFence(newSnapshot);
- JLA.setScopedValueBindings(newSnapshot.prev);
+ Thread.setScopedValueBindings(newSnapshot.prev);
Cache.invalidate(bitmask);
}
}
@@ -488,12 +578,46 @@ public final class ScopedValue {
* @return the result
* @throws Exception if the operation completes with an exception
*/
- public static R where(ScopedValue key,
- T value,
- Callable extends R> op) throws Exception {
+ public static R callWhere(ScopedValue key,
+ T value,
+ Callable extends R> op) throws Exception {
return where(key, value).call(op);
}
+ /**
+ * Invokes a supplier of results with a {@code ScopedValue} bound to a value
+ * in the current thread. When the operation completes (normally or with an
+ * exception), the {@code ScopedValue} will revert to being unbound, or revert to
+ * its previous value when previously bound, in the current thread.
+ *
+ * Scoped values are intended to be used in a structured manner .
+ * If {@code op} creates a {@link StructuredTaskScope} but does not {@linkplain
+ * StructuredTaskScope#close() close} it, then exiting {@code op} causes the
+ * underlying construct of each {@code StructuredTaskScope} created in the
+ * dynamic scope to be closed. This may require blocking until all child threads
+ * have completed their sub-tasks. The closing is done in the reverse order that
+ * they were created. Once closed, {@link StructureViolationException} is thrown.
+ *
+ * @implNote
+ * This method is implemented to be equivalent to:
+ * {@snippet lang=java :
+ * // @link substring="get" target="Carrier#get(Supplier)" :
+ * ScopedValue.where(key, value).get(op);
+ * }
+ *
+ * @param key the {@code ScopedValue} key
+ * @param value the value, can be {@code null}
+ * @param the type of the value
+ * @param the result type
+ * @param op the operation to call
+ * @return the result
+ */
+ public static R getWhere(ScopedValue key,
+ T value,
+ Supplier extends R> op) {
+ return where(key, value).get(op);
+ }
+
/**
* Run an operation with a {@code ScopedValue} bound to a value in the current
* thread. When the operation completes (normally or with an exception), the
@@ -520,7 +644,7 @@ public final class ScopedValue {
* @param the type of the value
* @param op the operation to call
*/
- public static void where(ScopedValue key, T value, Runnable op) {
+ public static void runWhere(ScopedValue key, T value, Runnable op) {
where(key, value).run(op);
}
@@ -642,11 +766,11 @@ public final class ScopedValue {
}
private static Object[] scopedValueCache() {
- return JLA.scopedValueCache();
+ return Thread.scopedValueCache();
}
private static void setScopedValueCache(Object[] cache) {
- JLA.setScopedValueCache(cache);
+ Thread.setScopedValueCache(cache);
}
// Special value to indicate this is a newly-created Thread
@@ -662,24 +786,24 @@ public final class ScopedValue {
// 3: A Snapshot instance: this contains one or more scoped value
// bindings.
// 4: null: there may be some bindings in this Thread, but we don't know
- // where they are. We must invoke JLA.findScopedValueBindings() to walk
+ // where they are. We must invoke Thread.findScopedValueBindings() to walk
// the stack to find them.
- Object bindings = JLA.scopedValueBindings();
+ Object bindings = Thread.scopedValueBindings();
if (bindings == NEW_THREAD_BINDINGS) {
// This must be a new thread
return Snapshot.EMPTY_SNAPSHOT;
}
if (bindings == null) {
// Search the stack
- bindings = JLA.findScopedValueBindings();
+ bindings = Thread.findScopedValueBindings();
if (bindings == null) {
// Nothing on the stack.
bindings = Snapshot.EMPTY_SNAPSHOT;
}
}
assert (bindings != null);
- JLA.setScopedValueBindings(bindings);
+ Thread.setScopedValueBindings(bindings);
return (Snapshot) bindings;
}
@@ -732,7 +856,7 @@ public final class ScopedValue {
private static final int MAX_CACHE_SIZE = 16;
static {
- final String propertyName = "jdk.incubator.concurrent.ScopedValue.cacheSize";
+ final String propertyName = "java.lang.ScopedValue.cacheSize";
var sizeString = GetPropertyAction.privilegedGetProperty(propertyName, "16");
var cacheSize = Integer.valueOf(sizeString);
if (cacheSize < 2 || cacheSize > MAX_CACHE_SIZE) {
diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java
index 6de8508d248..9d9dbf886e6 100644
--- a/src/java.base/share/classes/java/lang/System.java
+++ b/src/java.base/share/classes/java/lang/System.java
@@ -2621,19 +2621,6 @@ public final class System {
return Thread.scopedValueBindings();
}
- public Object findScopedValueBindings() {
- return Thread.findScopedValueBindings();
- }
-
- public void setScopedValueBindings(Object bindings) {
- Thread.setScopedValueBindings(bindings);
- }
-
- @ForceInline
- public void ensureMaterializedForStackWalk(Object value) {
- Thread.ensureMaterializedForStackWalk(value);
- }
-
public Continuation getContinuation(Thread thread) {
return thread.getContinuation();
}
diff --git a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java
index 6d33247df30..64c124a842a 100644
--- a/src/java.base/share/classes/java/lang/Thread.java
+++ b/src/java.base/share/classes/java/lang/Thread.java
@@ -37,9 +37,9 @@ import java.util.Map;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.StructureViolationException;
import java.util.concurrent.locks.LockSupport;
import jdk.internal.event.ThreadSleepEvent;
-import jdk.internal.misc.StructureViolationExceptions;
import jdk.internal.misc.TerminatingThreadLocal;
import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM;
@@ -321,7 +321,7 @@ public class Thread implements Runnable {
// bindings established for running/calling an operation
Object bindings = snapshot.scopedValueBindings();
if (currentThread().scopedValueBindings != bindings) {
- StructureViolationExceptions.throwException("Scoped value bindings have changed");
+ throw new StructureViolationException("Scoped value bindings have changed");
}
this.scopedValueBindings = bindings;
diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java b/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java
similarity index 89%
rename from src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java
rename to src/java.base/share/classes/java/util/concurrent/StructureViolationException.java
index 80bd6e4fe23..36224483f8b 100644
--- a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructureViolationException.java
+++ b/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 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
@@ -22,15 +22,18 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
-package jdk.incubator.concurrent;
+package java.util.concurrent;
+
+import jdk.internal.javac.PreviewFeature;
/**
* Thrown when a structure violation is detected.
*
* @see StructuredTaskScope#close()
*
- * @since 19
+ * @since 21
*/
+@PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY)
public final class StructureViolationException extends RuntimeException {
@java.io.Serial
private static final long serialVersionUID = -7705327650798235468L;
diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java
new file mode 100644
index 00000000000..8e995c79c4e
--- /dev/null
+++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java
@@ -0,0 +1,1273 @@
+/*
+ * Copyright (c) 2021, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package java.util.concurrent;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import jdk.internal.javac.PreviewFeature;
+import jdk.internal.misc.ThreadFlock;
+
+/**
+ * A basic API for structured concurrency . {@code StructuredTaskScope} supports
+ * cases where a task splits into several concurrent subtasks, and where the subtasks must
+ * complete before the main task continues. A {@code StructuredTaskScope} can be used to
+ * ensure that the lifetime of a concurrent operation is confined by a syntax block ,
+ * just like that of a sequential operation in structured programming.
+ *
+ * Basic operation
+ *
+ * A {@code StructuredTaskScope} is created with one of its public constructors. It defines
+ * the {@link #fork(Callable) fork} method to start a thread to execute a subtask, the {@link
+ * #join() join} method to wait for all subtasks to finish, and the {@link #close() close}
+ * method to close the task scope. The API is intended to be used with the {@code
+ * try-with-resources} statement. The intention is that code in the try block
+ * uses the {@code fork} method to fork threads to execute the subtasks, wait for the
+ * subtasks to finish with the {@code join} method, and then process the results .
+ * A call to the {@code fork} method returns a {@link Subtask Subtask} to representing
+ * the forked subtask . Once {@code join} is called, the {@code Subtask} can be
+ * used to get the result completed successfully, or the exception if the subtask failed.
+ * {@snippet lang=java :
+ * Callable task1 = ...
+ * Callable task1 = ...
+ *
+ * try (var scope = new StructuredTaskScope()) {
+ *
+ * Subtask subtask1 = scope.fork(task1); // @highlight substring="fork"
+ * Subtask subtask2 = scope.fork(task2); // @highlight substring="fork"
+ *
+ * scope.join(); // @highlight substring="join"
+ *
+ * ... process results/exceptions ...
+ *
+ * } // close // @highlight substring="close"
+ * }
+ * The following example forks a collection of homogeneous subtasks, waits for all of
+ * them to complete with the {@code join} method, and uses the {@link Subtask.State
+ * Subtask.State} to partition the subtasks into a set of the subtasks that completed
+ * successfully and another for the subtasks that failed.
+ * {@snippet lang=java :
+ * List> callables = ...
+ *
+ * try (var scope = new StructuredTaskScope()) {
+ *
+ * List> subtasks = callables.stream().map(scope::fork).toList();
+ *
+ * scope.join();
+ *
+ * Map>> map = subtasks.stream()
+ * .collect(Collectors.partitioningBy(h -> h.state() == Subtask.State.SUCCESS,
+ * Collectors.toSet()));
+ *
+ * } // close
+ * }
+ *
+ * To ensure correct usage, the {@code join} and {@code close} methods may only be
+ * invoked by the owner (the thread that opened/created the task scope), and the
+ * {@code close} method throws an exception after closing if the owner did not invoke the
+ * {@code join} method after forking.
+ *
+ *
{@code StructuredTaskScope} defines the {@link #shutdown() shutdown} method to shut
+ * down a task scope without closing it. The {@code shutdown()} method cancels all
+ * unfinished subtasks by {@linkplain Thread#interrupt() interrupting} the threads. It
+ * prevents new threads from starting in the task scope. If the owner is waiting in the
+ * {@code join} method then it will wakeup.
+ *
+ *
Shutdown is used for short-circuiting and allow subclasses to implement
+ * policy that does not require all subtasks to finish.
+ *
+ *
Subclasses with policies for common cases
+ *
+ * Two subclasses of {@code StructuredTaskScope} are defined to implement policy for
+ * common cases:
+ *
+ * {@link ShutdownOnSuccess ShutdownOnSuccess} captures the result of the first
+ * subtask to complete successfully. Once captured, it shuts down the task scope to
+ * interrupt unfinished threads and wakeup the owner. This class is intended for cases
+ * where the result of any subtask will do ("invoke any") and where there is no need to
+ * wait for results of other unfinished subtasks. It defines methods to get the first
+ * result or throw an exception if all subtasks fail.
+ * {@link ShutdownOnFailure ShutdownOnFailure} captures the exception of the first
+ * subtask to fail. Once captured, it shuts down the task scope to interrupt unfinished
+ * threads and wakeup the owner. This class is intended for cases where the results of all
+ * subtasks are required ("invoke all"); if any subtask fails then the results of other
+ * unfinished subtasks are no longer needed. If defines methods to throw an exception if
+ * any of the subtasks fail.
+ *
+ *
+ * The following are two examples that use the two classes. In both cases, a pair of
+ * subtasks are forked to fetch resources from two URL locations "left" and "right". The
+ * first example creates a ShutdownOnSuccess object to capture the result of the first
+ * subtask to complete successfully, cancelling the other by way of shutting down the task
+ * scope. The main task waits in {@code join} until either subtask completes with a result
+ * or both subtasks fail. It invokes {@link ShutdownOnSuccess#result(Function)
+ * result(Function)} method to get the captured result. If both subtasks fail then this
+ * method throws a {@code WebApplicationException} with the exception from one of the
+ * subtasks as the cause.
+ * {@snippet lang=java :
+ * try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
+ *
+ * scope.fork(() -> fetch(left));
+ * scope.fork(() -> fetch(right));
+ *
+ * scope.join();
+ *
+ * // @link regex="result(?=\()" target="ShutdownOnSuccess#result" :
+ * String result = scope.result(e -> new WebApplicationException(e));
+ *
+ * ...
+ * }
+ * }
+ * The second example creates a ShutdownOnFailure object to capture the exception of the
+ * first subtask to fail, cancelling the other by way of shutting down the task scope. The
+ * main task waits in {@link #joinUntil(Instant)} until both subtasks complete with a
+ * result, either fails, or a deadline is reached. It invokes {@link
+ * ShutdownOnFailure#throwIfFailed(Function) throwIfFailed(Function)} to throw an exception
+ * if either subtask fails. This method is a no-op if both subtasks complete successfully.
+ * The example uses {@link Supplier#get()} to get the result of each subtask. Using
+ * {@code Supplier} instead of {@code Subtask} is preferred for common cases where the
+ * object returned by fork is only used to get the result of a subtask that completed
+ * successfully.
+ * {@snippet lang=java :
+ * Instant deadline = ...
+ *
+ * try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
+ *
+ * Supplier supplier1 = scope.fork(() -> query(left));
+ * Supplier supplier2 = scope.fork(() -> query(right));
+ *
+ * scope.joinUntil(deadline);
+ *
+ * // @link substring="throwIfFailed" target="ShutdownOnFailure#throwIfFailed" :
+ * scope.throwIfFailed(e -> new WebApplicationException(e));
+ *
+ * // both subtasks completed successfully
+ * String result = Stream.of(supplier1, supplier2)
+ * .map(Supplier::get)
+ * .collect(Collectors.joining(", ", "{ ", " }"));
+ *
+ * ...
+ * }
+ * }
+ *
+ * Extending StructuredTaskScope
+ *
+ * {@code StructuredTaskScope} can be extended, and the {@link #handleComplete(Subtask)
+ * handleComplete} method overridden, to implement policies other than those implemented
+ * by {@code ShutdownOnSuccess} and {@code ShutdownOnFailure}. A subclass may, for example,
+ * collect the results of subtasks that complete successfully and ignore subtasks that
+ * fail. It may collect exceptions when subtasks fail. It may invoke the {@link #shutdown()
+ * shutdown} method to shut down and cause {@link #join() join} to wakeup when some
+ * condition arises.
+ *
+ * A subclass will typically define methods to make available results, state, or other
+ * outcome to code that executes after the {@code join} method. A subclass that collects
+ * results and ignores subtasks that fail may define a method that returns the results.
+ * A subclass that implements a policy to shut down when a subtask fails may define a
+ * method to get the exception of the first subtask to fail.
+ *
+ *
The following is an example of a simple {@code StructuredTaskScope} implementation
+ * that collects homogenous subtasks that complete successfully. It defines the method
+ * "{@code completedSuccessfully()}" that the main task can invoke after it joins.
+ * {@snippet lang=java :
+ * class CollectingScope extends StructuredTaskScope {
+ * private final Queue> subtasks = new LinkedTransferQueue<>();
+ *
+ * @Override
+ * protected void handleComplete(Subtask extends T> subtask) {
+ * if (subtask.state() == Subtask.State.SUCCESS) {
+ * subtasks.add(subtask);
+ * }
+ * }
+ *
+ * @Override
+ * public CollectingScope join() throws InterruptedException {
+ * super.join();
+ * return this;
+ * }
+ *
+ * public Stream> completedSuccessfully() {
+ * // @link substring="ensureOwnerAndJoined" target="ensureOwnerAndJoined" :
+ * super.ensureOwnerAndJoined();
+ * return subtasks.stream();
+ * }
+ * }
+ * }
+ * The implementations of the {@code completedSuccessfully()} method in the example
+ * invokes {@link #ensureOwnerAndJoined()} to ensure that the method can only be invoked
+ * by the owner thread and only after it has joined.
+ *
+ *
+ *
+ * Task scopes form a tree where parent-child relations are established implicitly when
+ * opening a new task scope:
+ *
+ * A parent-child relation is established when a thread started in a task scope
+ * opens its own task scope. A thread started in task scope "A" that opens task scope
+ * "B" establishes a parent-child relation where task scope "A" is the parent of task
+ * scope "B".
+ * A parent-child relation is established with nesting. If a thread opens task
+ * scope "B", then opens task scope "C" (before it closes "B"), then the enclosing task
+ * scope "B" is the parent of the nested task scope "C".
+ *
+ *
+ * The descendants of a task scope are the child task scopes that it is a parent
+ * of, plus the descendants of the child task scopes, recursively.
+ *
+ * The tree structure supports:
+ *
+ * Inheritance of {@linkplain ScopedValue scoped values} across threads.
+ * Confinement checks. The phrase "threads contained in the task scope" in method
+ * descriptions means threads started in the task scope or descendant scopes.
+ *
+ *
+ * The following example demonstrates the inheritance of a scoped value. A scoped
+ * value {@code USERNAME} is bound to the value "{@code duke}". A {@code StructuredTaskScope}
+ * is created and its {@code fork} method invoked to start a thread to execute {@code
+ * childTask}. The thread inherits the scoped value bindings captured when
+ * creating the task scope. The code in {@code childTask} uses the value of the scoped
+ * value and so reads the value "{@code duke}".
+ * {@snippet lang=java :
+ * private static final ScopedValue USERNAME = ScopedValue.newInstance();
+ *
+ * // @link substring="runWhere" target="ScopedValue#runWhere(ScopedValue, Object, Runnable)" :
+ * ScopedValue.runWhere(USERNAME, "duke", () -> {
+ * try (var scope = new StructuredTaskScope()) {
+ *
+ * scope.fork(() -> childTask()); // @highlight substring="fork"
+ * ...
+ * }
+ * });
+ *
+ * ...
+ *
+ * String childTask() {
+ * // @link substring="get" target="ScopedValue#get()" :
+ * String name = USERNAME.get(); // "duke"
+ * ...
+ * }
+ * }
+ *
+ * {@code StructuredTaskScope} does not define APIs that exposes the tree structure
+ * at this time.
+ *
+ *
Unless otherwise specified, passing a {@code null} argument to a constructor
+ * or method in this class will cause a {@link NullPointerException} to be thrown.
+ *
+ *
Memory consistency effects
+ *
+ * Actions in the owner thread of, or a thread contained in, the task scope prior to
+ * {@linkplain #fork forking} of a subtask
+ *
+ * happen-before any actions taken by that subtask, which in turn happen-before
+ * the subtask result is {@linkplain Subtask#get() retrieved} or happen-before any
+ * actions taken in a thread after {@linkplain #join() joining} of the task scope.
+ *
+ * @jls 17.4.5 Happens-before Order
+ *
+ * @param the result type of tasks executed in the task scope
+ * @since 21
+ */
+@PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY)
+public class StructuredTaskScope implements AutoCloseable {
+ private final ThreadFactory factory;
+ private final ThreadFlock flock;
+ private final ReentrantLock shutdownLock = new ReentrantLock();
+
+ // states: OPEN -> SHUTDOWN -> CLOSED
+ private static final int OPEN = 0; // initial state
+ private static final int SHUTDOWN = 1;
+ private static final int CLOSED = 2;
+
+ // state: set to SHUTDOWN by any thread, set to CLOSED by owner, read by any thread
+ private volatile int state;
+
+ // Counters to support checking that the task scope owner joins before processing
+ // results and attempts join before closing the task scope. These counters are
+ // accessed only by the owner thread.
+ private int forkRound; // incremented when the first subtask is forked after join
+ private int lastJoinAttempted; // set to the current fork round when join is attempted
+ private int lastJoinCompleted; // set to the current fork round when join completes
+
+ /**
+ * Represents a subtask forked with {@link #fork(Callable)}.
+ * @param the result type
+ * @since 21
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY)
+ public sealed interface Subtask extends Supplier permits SubtaskImpl {
+ /**
+ * {@return the value returning task provided to the {@code fork} method}
+ *
+ * @apiNote Task objects with unique identity may be used for correlation by
+ * implementations of {@link #handleComplete(Subtask) handleComplete}.
+ */
+ Callable extends T> task();
+
+ /**
+ * Represents the state of a subtask.
+ * @see Subtask#state()
+ * @since 21
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY)
+ enum State {
+ /**
+ * The subtask result or exception is not available. This state indicates that
+ * the subtask was forked but has not completed, it completed after the task
+ * scope was {@linkplain #shutdown() shut down}, or it was forked after the
+ * task scope was shut down.
+ */
+ UNAVAILABLE,
+ /**
+ * The subtask completed successfully with a result. The {@link Subtask#get()
+ * Subtask.get()} method can be used to obtain the result. This is a terminal
+ * state.
+ */
+ SUCCESS,
+ /**
+ * The subtask failed with an exception. The {@link Subtask#exception()
+ * Subtask.exception()} method can be used to obtain the exception. This is a
+ * terminal state.
+ */
+ FAILED,
+ }
+
+ /**
+ * {@return the state of the subtask}
+ */
+ State state();
+
+ /**
+ * Returns the result of the subtask.
+ *
+ * To ensure correct usage, if the scope owner {@linkplain #fork(Callable) forks}
+ * a subtask, then it must join (with {@link #join() join} or {@link #joinUntil(Instant)
+ * joinUntil}) before it can obtain the result of the subtask.
+ *
+ * @return the possibly-null result
+ * @throws IllegalStateException if the subtask has not completed, did not complete
+ * successfully, or the current thread is the task scope owner and did not join
+ * after forking
+ * @see State#SUCCESS
+ */
+ T get();
+
+ /**
+ * {@return the exception thrown by the subtask}
+ *
+ *
To ensure correct usage, if the scope owner {@linkplain #fork(Callable) forks}
+ * a subtask, then it must join (with {@link #join() join} or {@link #joinUntil(Instant)
+ * joinUntil}) before it can obtain the exception thrown by the subtask.
+ *
+ * @throws IllegalStateException if the subtask has not completed, completed with
+ * a result, or the current thread is the task scope owner and did not join after
+ * forking
+ * @see State#FAILED
+ */
+ Throwable exception();
+ }
+
+ /**
+ * Creates a structured task scope with the given name and thread factory. The task
+ * scope is optionally named for the purposes of monitoring and management. The thread
+ * factory is used to {@link ThreadFactory#newThread(Runnable) create} threads when
+ * subtasks are {@linkplain #fork(Callable) forked}. The task scope is owned by the
+ * current thread.
+ *
+ *
Construction captures the current thread's {@linkplain ScopedValue scoped value}
+ * bindings for inheritance by threads started in the task scope. The
+ * Tree Structure section in the class description details
+ * how parent-child relations are established implicitly for the purpose of inheritance
+ * of scoped value bindings.
+ *
+ * @param name the name of the task scope, can be null
+ * @param factory the thread factory
+ */
+ public StructuredTaskScope(String name, ThreadFactory factory) {
+ this.factory = Objects.requireNonNull(factory, "'factory' is null");
+ if (name == null)
+ name = Objects.toIdentityString(this);
+ this.flock = ThreadFlock.open(name);
+ }
+
+ /**
+ * Creates an unnamed structured task scope that creates virtual threads. The task
+ * scope is owned by the current thread.
+ *
+ * @implSpec This constructor is equivalent to invoking the 2-arg constructor with a
+ * name of {@code null} and a thread factory that creates virtual threads.
+ */
+ public StructuredTaskScope() {
+ this(null, Thread.ofVirtual().factory());
+ }
+
+ private IllegalStateException newIllegalStateExceptionScopeClosed() {
+ return new IllegalStateException("Task scope is closed");
+ }
+
+ private IllegalStateException newIllegalStateExceptionNoJoin() {
+ return new IllegalStateException("Owner did not join after forking subtasks");
+ }
+
+ /**
+ * Throws IllegalStateException if the scope is closed, returning the state if not
+ * closed.
+ */
+ private int ensureOpen() {
+ int s = state;
+ if (s == CLOSED)
+ throw newIllegalStateExceptionScopeClosed();
+ return s;
+ }
+
+ /**
+ * Throws WrongThreadException if the current thread is not the owner.
+ */
+ private void ensureOwner() {
+ if (Thread.currentThread() != flock.owner())
+ throw new WrongThreadException("Current thread not owner");
+ }
+
+ /**
+ * Throws WrongThreadException if the current thread is not the owner
+ * or a thread contained in the tree.
+ */
+ private void ensureOwnerOrContainsThread() {
+ Thread currentThread = Thread.currentThread();
+ if (currentThread != flock.owner() && !flock.containsThread(currentThread))
+ throw new WrongThreadException("Current thread not owner or thread in the tree");
+ }
+
+ /**
+ * Throws IllegalStateException if the current thread is the owner, and the owner did
+ * not join after forking a subtask in the given fork round.
+ */
+ private void ensureJoinedIfOwner(int round) {
+ if (Thread.currentThread() == flock.owner() && (round > lastJoinCompleted)) {
+ throw newIllegalStateExceptionNoJoin();
+ }
+ }
+
+ /**
+ * Ensures that the current thread is the owner of this task scope and that it joined
+ * (with {@link #join()} or {@link #joinUntil(Instant)}) after {@linkplain #fork(Callable)
+ * forking} subtasks.
+ *
+ * @apiNote This method can be used by subclasses that define methods to make available
+ * results, state, or other outcome to code intended to execute after the join method.
+ *
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws IllegalStateException if the task scope is open and task scope owner did
+ * not join after forking
+ */
+ protected final void ensureOwnerAndJoined() {
+ ensureOwner();
+ if (forkRound > lastJoinCompleted) {
+ throw newIllegalStateExceptionNoJoin();
+ }
+ }
+
+ /**
+ * Invoked by a subtask when it completes successfully or fails in this task scope.
+ * This method is not invoked if a subtask completes after the task scope is
+ * {@linkplain #shutdown() shut down}.
+ *
+ * @implSpec The default implementation throws {@code NullPointerException} if the
+ * subtask is {@code null}. It throws {@link IllegalArgumentException} if the subtask
+ * has not completed.
+ *
+ * @apiNote The {@code handleComplete} method should be thread safe. It may be
+ * invoked by several threads concurrently.
+ *
+ * @param subtask the subtask
+ *
+ * @throws IllegalArgumentException if called with a subtask that has not completed
+ */
+ protected void handleComplete(Subtask extends T> subtask) {
+ if (subtask.state() == Subtask.State.UNAVAILABLE)
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Starts a new thread in this task scope to execute a value-returning task, thus
+ * creating a subtask of this task scope.
+ *
+ *
The value-returning task is provided to this method as a {@link Callable}, the
+ * thread executes the task's {@link Callable#call() call} method. The thread is
+ * created with the task scope's {@link ThreadFactory}. It inherits the current thread's
+ * {@linkplain ScopedValue scoped value} bindings. The bindings must match the bindings
+ * captured when the task scope was created.
+ *
+ *
This method returns a {@link Subtask Subtask} to represent the forked
+ * subtask . The {@code Subtask} object can be used to obtain the result when
+ * the subtask completes successfully, or the exception when the subtask fails. To
+ * ensure correct usage, the {@link Subtask#get() get()} and {@link Subtask#exception()
+ * exception()} methods may only be called by the task scope owner after it has waited
+ * for all threads to finish with the {@link #join() join} or {@link #joinUntil(Instant)}
+ * methods. When the subtask completes, the thread invokes the {@link
+ * #handleComplete(Subtask) handleComplete} method to consume the completed subtask.
+ * If the task scope is {@linkplain #shutdown() shut down} before the subtask completes
+ * then the {@code handleComplete} method will not be invoked.
+ *
+ *
If this task scope is {@linkplain #shutdown() shutdown} (or in the process of
+ * shutting down) then the subtask will not run and the {@code handleComplete} method
+ * will not be invoked.
+ *
+ *
This method may only be invoked by the task scope owner or threads contained
+ * in the task scope.
+ *
+ * @implSpec This method may be overridden for customization purposes, wrapping tasks
+ * for example. If overridden, the subclass must invoke {@code super.fork} to start a
+ * new thread in this task scope.
+ *
+ * @param task the value-returning task for the thread to execute
+ * @param the result type
+ * @return the subtask
+ * @throws IllegalStateException if this task scope is closed
+ * @throws WrongThreadException if the current thread is not the task scope owner or a
+ * thread contained in the task scope
+ * @throws StructureViolationException if the current scoped value bindings are not
+ * the same as when the task scope was created
+ * @throws RejectedExecutionException if the thread factory rejected creating a
+ * thread to run the subtask
+ */
+ public Subtask fork(Callable extends U> task) {
+ Objects.requireNonNull(task, "'task' is null");
+ int s = ensureOpen(); // throws ISE if closed
+
+ // when forked by the owner, the subtask is forked in the current or next round
+ int round = -1;
+ if (Thread.currentThread() == flock.owner()) {
+ round = forkRound;
+ if (forkRound == lastJoinCompleted) {
+ // new round if first fork after join
+ round++;
+ }
+ }
+
+ SubtaskImpl subtask = new SubtaskImpl<>(this, task, round);
+ boolean started = false;
+
+ if (s < SHUTDOWN) {
+ // create thread to run task
+ Thread thread = factory.newThread(subtask);
+ if (thread == null) {
+ throw new RejectedExecutionException("Rejected by thread factory");
+ }
+
+ // attempt to start the thread
+ try {
+ flock.start(thread);
+ started = true;
+ } catch (IllegalStateException e) {
+ // shutdown by another thread, or underlying flock is shutdown due
+ // to unstructured use
+ }
+ }
+
+ // force owner to join if thread started
+ if (started && Thread.currentThread() == flock.owner() && round > forkRound) {
+ forkRound = round;
+ }
+
+ // return forked subtask or a subtask that did not run
+ return subtask;
+ }
+
+ /**
+ * Wait for all threads to finish or the task scope to shut down.
+ */
+ private void implJoin(Duration timeout)
+ throws InterruptedException, TimeoutException
+ {
+ ensureOwner();
+ lastJoinAttempted = forkRound;
+ int s = ensureOpen(); // throws ISE if closed
+ if (s == OPEN) {
+ // wait for all threads, wakeup, interrupt, or timeout
+ if (timeout != null) {
+ flock.awaitAll(timeout);
+ } else {
+ flock.awaitAll();
+ }
+ }
+ lastJoinCompleted = forkRound;
+ }
+
+ /**
+ * Wait for all threads in this task scope to finish or the task scope to shut down.
+ * This method waits until all threads started in this task scope finish execution,
+ * the {@link #shutdown() shutdown} method is invoked to shut down the task scope,
+ * or the current thread is {@linkplain Thread#interrupt() interrupted}.
+ *
+ *
This method may only be invoked by the task scope owner.
+ *
+ * @implSpec This method may be overridden for customization purposes or to return a
+ * more specific return type. If overridden, the subclass must invoke {@code
+ * super.join} to ensure that the method waits for threads in this task scope to
+ * finish.
+ *
+ * @return this task scope
+ * @throws IllegalStateException if this task scope is closed
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws InterruptedException if interrupted while waiting
+ */
+ public StructuredTaskScope join() throws InterruptedException {
+ try {
+ implJoin(null);
+ } catch (TimeoutException e) {
+ throw new InternalError();
+ }
+ return this;
+ }
+
+ /**
+ * Wait for all threads in this task scope to finish or the task scope to shut down,
+ * up to the given deadline. This method waits until all threads started in the task
+ * scope finish execution, the {@link #shutdown() shutdown} method is invoked to
+ * shut down the task scope, the current thread is {@linkplain Thread#interrupt()
+ * interrupted}, or the deadline is reached.
+ *
+ * This method may only be invoked by the task scope owner.
+ *
+ * @implSpec This method may be overridden for customization purposes or to return a
+ * more specific return type. If overridden, the subclass must invoke {@code
+ * super.joinUntil} to ensure that the method waits for threads in this task scope to
+ * finish.
+ *
+ * @param deadline the deadline
+ * @return this task scope
+ * @throws IllegalStateException if this task scope is closed
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws InterruptedException if interrupted while waiting
+ * @throws TimeoutException if the deadline is reached while waiting
+ */
+ public StructuredTaskScope joinUntil(Instant deadline)
+ throws InterruptedException, TimeoutException
+ {
+ Duration timeout = Duration.between(Instant.now(), deadline);
+ implJoin(timeout);
+ return this;
+ }
+
+ /**
+ * Interrupt all unfinished threads.
+ */
+ private void implInterruptAll() {
+ flock.threads()
+ .filter(t -> t != Thread.currentThread())
+ .forEach(t -> {
+ try {
+ t.interrupt();
+ } catch (Throwable ignore) { }
+ });
+ }
+
+ @SuppressWarnings("removal")
+ private void interruptAll() {
+ if (System.getSecurityManager() == null) {
+ implInterruptAll();
+ } else {
+ PrivilegedAction pa = () -> {
+ implInterruptAll();
+ return null;
+ };
+ AccessController.doPrivileged(pa);
+ }
+ }
+
+ /**
+ * Shutdown the task scope if not already shutdown. Return true if this method
+ * shutdowns the task scope, false if already shutdown.
+ */
+ private boolean implShutdown() {
+ shutdownLock.lock();
+ try {
+ if (state < SHUTDOWN) {
+ // prevent new threads from starting
+ flock.shutdown();
+
+ // set status before interrupting tasks
+ state = SHUTDOWN;
+
+ // interrupt all unfinished threads
+ interruptAll();
+
+ return true;
+ } else {
+ // already shutdown
+ return false;
+ }
+ } finally {
+ shutdownLock.unlock();
+ }
+ }
+
+ /**
+ * Shut down this task scope without closing it. Shutting down a task scope prevents
+ * new threads from starting, interrupts all unfinished threads, and causes the
+ * {@link #join() join} method to wakeup. Shutdown is useful for cases where the
+ * results of unfinished subtasks are no longer needed. It will typically be called
+ * by the {@link #handleComplete(Subtask)} implementation of a subclass that
+ * implements a policy to discard unfinished tasks once some outcome is reached.
+ *
+ * More specifically, this method:
+ *
+ * {@linkplain Thread#interrupt() Interrupts} all unfinished threads in the
+ * task scope (except the current thread).
+ * Wakes up the task scope owner if it is waiting in {@link #join()} or {@link
+ * #joinUntil(Instant)}. If the task scope owner is not waiting then its next call to
+ * {@code join} or {@code joinUntil} will return immediately.
+ *
+ *
+ * This method may only be invoked by the task scope owner or threads contained
+ * in the task scope.
+ *
+ * @implSpec This method may be overridden for customization purposes. If overridden,
+ * the subclass must invoke {@code super.shutdown} to ensure that the method shuts
+ * down the task scope.
+ *
+ * @apiNote
+ * There may be threads that have not finished because they are executing code that
+ * did not respond (or respond promptly) to thread interrupt. This method does not wait
+ * for these threads. When the owner invokes the {@link #close() close} method
+ * to close the task scope then it will wait for the remaining threads to finish.
+ *
+ * @throws IllegalStateException if this task scope is closed
+ * @throws WrongThreadException if the current thread is not the task scope owner or
+ * a thread contained in the task scope
+ * @see #isShutdown()
+ */
+ public void shutdown() {
+ ensureOwnerOrContainsThread();
+ int s = ensureOpen(); // throws ISE if closed
+ if (s < SHUTDOWN && implShutdown())
+ flock.wakeup();
+ }
+
+ /**
+ * {@return true if this task scope is shutdown, otherwise false}
+ * @see #shutdown()
+ */
+ public final boolean isShutdown() {
+ return state >= SHUTDOWN;
+ }
+
+ /**
+ * Closes this task scope.
+ *
+ *
This method first shuts down the task scope (as if by invoking the {@link
+ * #shutdown() shutdown} method). It then waits for the threads executing any
+ * unfinished tasks to finish. If interrupted, this method will continue to wait for
+ * the threads to finish before completing with the interrupt status set.
+ *
+ *
This method may only be invoked by the task scope owner. If the task scope
+ * is already closed then the task scope owner invoking this method has no effect.
+ *
+ *
A {@code StructuredTaskScope} is intended to be used in a structured
+ * manner . If this method is called to close a task scope before nested task
+ * scopes are closed then it closes the underlying construct of each nested task scope
+ * (in the reverse order that they were created in), closes this task scope, and then
+ * throws {@link StructureViolationException}.
+ * Similarly, if this method is called to close a task scope while executing with
+ * {@linkplain ScopedValue scoped value} bindings, and the task scope was created
+ * before the scoped values were bound, then {@code StructureViolationException} is
+ * thrown after closing the task scope.
+ * If a thread terminates without first closing task scopes that it owns then
+ * termination will cause the underlying construct of each of its open tasks scopes to
+ * be closed. Closing is performed in the reverse order that the task scopes were
+ * created in. Thread termination may therefore be delayed when the task scope owner
+ * has to wait for threads forked in these task scopes to finish.
+ *
+ * @implSpec This method may be overridden for customization purposes. If overridden,
+ * the subclass must invoke {@code super.close} to close the task scope.
+ *
+ * @throws IllegalStateException thrown after closing the task scope if the task scope
+ * owner did not attempt to join after forking
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws StructureViolationException if a structure violation was detected
+ */
+ @Override
+ public void close() {
+ ensureOwner();
+ int s = state;
+ if (s == CLOSED)
+ return;
+
+ try {
+ if (s < SHUTDOWN)
+ implShutdown();
+ flock.close();
+ } finally {
+ state = CLOSED;
+ }
+
+ // throw ISE if the owner didn't attempt to join after forking
+ if (forkRound > lastJoinAttempted) {
+ lastJoinCompleted = forkRound;
+ throw newIllegalStateExceptionNoJoin();
+ }
+ }
+
+ @Override
+ public String toString() {
+ String name = flock.name();
+ return switch (state) {
+ case OPEN -> name;
+ case SHUTDOWN -> name + "/shutdown";
+ case CLOSED -> name + "/closed";
+ default -> throw new InternalError();
+ };
+ }
+
+ /**
+ * Subtask implementation, runs the task specified to the fork method.
+ */
+ private static final class SubtaskImpl implements Subtask, Runnable {
+ private static final AltResult RESULT_NULL = new AltResult(Subtask.State.SUCCESS);
+
+ private record AltResult(Subtask.State state, Throwable exception) {
+ AltResult(Subtask.State state) {
+ this(state, null);
+ }
+ }
+
+ private final StructuredTaskScope super T> scope;
+ private final Callable extends T> task;
+ private final int round;
+ private volatile Object result;
+
+ SubtaskImpl(StructuredTaskScope super T> scope,
+ Callable extends T> task,
+ int round) {
+ this.scope = scope;
+ this.task = task;
+ this.round = round;
+ }
+
+ @Override
+ public void run() {
+ T result = null;
+ Throwable ex = null;
+ try {
+ result = task.call();
+ } catch (Throwable e) {
+ ex = e;
+ }
+
+ // nothing to do if task scope is shutdown
+ if (scope.isShutdown())
+ return;
+
+ // capture result or exception, invoke handleComplete
+ if (ex == null) {
+ this.result = (result != null) ? result : RESULT_NULL;
+ } else {
+ this.result = new AltResult(State.FAILED, ex);
+ }
+ scope.handleComplete(this);
+ }
+
+ @Override
+ public Callable extends T> task() {
+ return task;
+ }
+
+ @Override
+ public Subtask.State state() {
+ Object result = this.result;
+ if (result == null) {
+ return State.UNAVAILABLE;
+ } else if (result instanceof AltResult alt) {
+ // null or failed
+ return alt.state();
+ } else {
+ return State.SUCCESS;
+ }
+ }
+
+ @Override
+ public T get() {
+ scope.ensureJoinedIfOwner(round);
+ Object result = this.result;
+ if (result instanceof AltResult) {
+ if (result == RESULT_NULL) return null;
+ } else if (result != null) {
+ @SuppressWarnings("unchecked")
+ T r = (T) result;
+ return r;
+ }
+ throw new IllegalStateException("Subtask not completed or did not complete successfully");
+ }
+
+ @Override
+ public Throwable exception() {
+ scope.ensureJoinedIfOwner(round);
+ Object result = this.result;
+ if (result instanceof AltResult alt && alt.state() == State.FAILED) {
+ return alt.exception();
+ }
+ throw new IllegalStateException("Subtask not completed or did not complete with exception");
+ }
+
+ @Override
+ public String toString() {
+ String stateAsString = switch (state()) {
+ case UNAVAILABLE -> "[Unavailable]";
+ case SUCCESS -> "[Completed successfully]";
+ case FAILED -> {
+ Throwable ex = ((AltResult) result).exception();
+ yield "[Failed: " + ex + "]";
+ }
+ };
+ return Objects.toIdentityString(this ) + stateAsString;
+ }
+ }
+
+ /**
+ * A {@code StructuredTaskScope} that captures the result of the first subtask to
+ * complete {@linkplain Subtask.State#SUCCESS successfully}. Once captured, it
+ * invokes the {@linkplain #shutdown() shutdown} method to interrupt unfinished threads
+ * and wakeup the task scope owner. The policy implemented by this class is intended
+ * for cases where the result of any subtask will do ("invoke any") and where the
+ * results of other unfinished subtasks are no longer needed.
+ *
+ * Unless otherwise specified, passing a {@code null} argument to a method
+ * in this class will cause a {@link NullPointerException} to be thrown.
+ *
+ * @param the result type
+ * @since 21
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY)
+ public static final class ShutdownOnSuccess extends StructuredTaskScope {
+ private static final Object RESULT_NULL = new Object();
+ private static final VarHandle FIRST_RESULT;
+ private static final VarHandle FIRST_EXCEPTION;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ FIRST_RESULT = l.findVarHandle(ShutdownOnSuccess.class, "firstResult", Object.class);
+ FIRST_EXCEPTION = l.findVarHandle(ShutdownOnSuccess.class, "firstException", Throwable.class);
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+ private volatile Object firstResult;
+ private volatile Throwable firstException;
+
+ /**
+ * Constructs a new {@code ShutdownOnSuccess} with the given name and thread factory.
+ * The task scope is optionally named for the purposes of monitoring and management.
+ * The thread factory is used to {@link ThreadFactory#newThread(Runnable) create}
+ * threads when subtasks are {@linkplain #fork(Callable) forked}. The task scope
+ * is owned by the current thread.
+ *
+ * Construction captures the current thread's {@linkplain ScopedValue scoped
+ * value} bindings for inheritance by threads started in the task scope. The
+ * Tree Structure section in the class description
+ * details how parent-child relations are established implicitly for the purpose
+ * of inheritance of scoped value bindings.
+ *
+ * @param name the name of the task scope, can be null
+ * @param factory the thread factory
+ */
+ public ShutdownOnSuccess(String name, ThreadFactory factory) {
+ super(name, factory);
+ }
+
+ /**
+ * Constructs a new unnamed {@code ShutdownOnSuccess} that creates virtual threads.
+ *
+ * @implSpec This constructor is equivalent to invoking the 2-arg constructor with
+ * a name of {@code null} and a thread factory that creates virtual threads.
+ */
+ public ShutdownOnSuccess() {
+ this(null, Thread.ofVirtual().factory());
+ }
+
+ @Override
+ protected void handleComplete(Subtask extends T> subtask) {
+ super.handleComplete(subtask);
+
+ if (firstResult != null) {
+ // already captured a result
+ return;
+ }
+
+ if (subtask.state() == Subtask.State.SUCCESS) {
+ // task succeeded
+ T result = subtask.get();
+ Object r = (result != null) ? result : RESULT_NULL;
+ if (FIRST_RESULT.compareAndSet(this, null, r)) {
+ super.shutdown();
+ }
+ } else if (firstException == null) {
+ // capture the exception thrown by the first subtask that failed
+ FIRST_EXCEPTION.compareAndSet(this, null, subtask.exception());
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return this task scope
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws WrongThreadException {@inheritDoc}
+ */
+ @Override
+ public ShutdownOnSuccess join() throws InterruptedException {
+ super.join();
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return this task scope
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws WrongThreadException {@inheritDoc}
+ */
+ @Override
+ public ShutdownOnSuccess joinUntil(Instant deadline)
+ throws InterruptedException, TimeoutException
+ {
+ super.joinUntil(deadline);
+ return this;
+ }
+
+ /**
+ * {@return the result of the first subtask that completed {@linkplain
+ * Subtask.State#SUCCESS successfully}}
+ *
+ * When no subtask completed successfully, but a subtask {@linkplain
+ * Subtask.State#FAILED failed} then {@code ExecutionException} is thrown with
+ * the subtask's exception as the {@linkplain Throwable#getCause() cause}.
+ *
+ * @throws ExecutionException if no subtasks completed successfully but at least
+ * one subtask failed
+ * @throws IllegalStateException if the handleComplete method was not invoked with
+ * a completed subtask, or the task scope owner did not join after forking
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ */
+ public T result() throws ExecutionException {
+ return result(ExecutionException::new);
+ }
+
+ /**
+ * Returns the result of the first subtask that completed {@linkplain
+ * Subtask.State#SUCCESS successfully}, otherwise throws an exception produced
+ * by the given exception supplying function.
+ *
+ *
When no subtask completed successfully, but a subtask {@linkplain
+ * Subtask.State#FAILED failed}, then the exception supplying function is invoked
+ * with subtask's exception.
+ *
+ * @param esf the exception supplying function
+ * @param type of the exception to be thrown
+ * @return the result of the first subtask that completed with a result
+ *
+ * @throws X if no subtasks completed successfully but at least one subtask failed
+ * @throws IllegalStateException if the handleComplete method was not invoked with
+ * a completed subtask, or the task scope owner did not join after forking
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ */
+ public T result(Function esf) throws X {
+ Objects.requireNonNull(esf);
+ ensureOwnerAndJoined();
+
+ Object result = firstResult;
+ if (result == RESULT_NULL) {
+ return null;
+ } else if (result != null) {
+ @SuppressWarnings("unchecked")
+ T r = (T) result;
+ return r;
+ }
+
+ Throwable exception = firstException;
+ if (exception != null) {
+ X ex = esf.apply(exception);
+ Objects.requireNonNull(ex, "esf returned null");
+ throw ex;
+ }
+
+ throw new IllegalStateException("No completed subtasks");
+ }
+ }
+
+ /**
+ * A {@code StructuredTaskScope} that captures the exception of the first subtask to
+ * {@linkplain Subtask.State#FAILED fail}. Once captured, it invokes the {@linkplain
+ * #shutdown() shutdown} method to interrupt unfinished threads and wakeup the task
+ * scope owner. The policy implemented by this class is intended for cases where the
+ * results for all subtasks are required ("invoke all"); if any subtask fails then the
+ * results of other unfinished subtasks are no longer needed.
+ *
+ * Unless otherwise specified, passing a {@code null} argument to a method
+ * in this class will cause a {@link NullPointerException} to be thrown.
+ *
+ * @since 21
+ */
+ @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY)
+ public static final class ShutdownOnFailure extends StructuredTaskScope {
+ private static final VarHandle FIRST_EXCEPTION;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ FIRST_EXCEPTION = l.findVarHandle(ShutdownOnFailure.class, "firstException", Throwable.class);
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+ private volatile Throwable firstException;
+
+ /**
+ * Constructs a new {@code ShutdownOnFailure} with the given name and thread factory.
+ * The task scope is optionally named for the purposes of monitoring and management.
+ * The thread factory is used to {@link ThreadFactory#newThread(Runnable) create}
+ * threads when subtasks are {@linkplain #fork(Callable) forked}. The task scope
+ * is owned by the current thread.
+ *
+ * Construction captures the current thread's {@linkplain ScopedValue scoped
+ * value} bindings for inheritance by threads started in the task scope. The
+ * Tree Structure section in the class description
+ * details how parent-child relations are established implicitly for the purpose
+ * of inheritance of scoped value bindings.
+ *
+ * @param name the name of the task scope, can be null
+ * @param factory the thread factory
+ */
+ public ShutdownOnFailure(String name, ThreadFactory factory) {
+ super(name, factory);
+ }
+
+ /**
+ * Constructs a new unnamed {@code ShutdownOnFailure} that creates virtual threads.
+ *
+ * @implSpec This constructor is equivalent to invoking the 2-arg constructor with
+ * a name of {@code null} and a thread factory that creates virtual threads.
+ */
+ public ShutdownOnFailure() {
+ this(null, Thread.ofVirtual().factory());
+ }
+
+ @Override
+ protected void handleComplete(Subtask> subtask) {
+ super.handleComplete(subtask);
+ if (subtask.state() == Subtask.State.FAILED
+ && firstException == null
+ && FIRST_EXCEPTION.compareAndSet(this, null, subtask.exception())) {
+ super.shutdown();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return this task scope
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws WrongThreadException {@inheritDoc}
+ */
+ @Override
+ public ShutdownOnFailure join() throws InterruptedException {
+ super.join();
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return this task scope
+ * @throws IllegalStateException {@inheritDoc}
+ * @throws WrongThreadException {@inheritDoc}
+ */
+ @Override
+ public ShutdownOnFailure joinUntil(Instant deadline)
+ throws InterruptedException, TimeoutException
+ {
+ super.joinUntil(deadline);
+ return this;
+ }
+
+ /**
+ * Returns the exception of the first subtask that {@linkplain Subtask.State#FAILED
+ * failed}. If no subtasks failed then an empty {@code Optional} is returned.
+ *
+ * @return the exception for the first subtask to fail or an empty optional if no
+ * subtasks failed
+ *
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws IllegalStateException if the task scope owner did not join after forking
+ */
+ public Optional exception() {
+ ensureOwnerAndJoined();
+ return Optional.ofNullable(firstException);
+ }
+
+ /**
+ * Throws if a subtask {@linkplain Subtask.State#FAILED failed}.
+ * If any subtask failed with an exception then {@code ExecutionException} is
+ * thrown with the exception of the first subtask to fail as the {@linkplain
+ * Throwable#getCause() cause}. This method does nothing if no subtasks failed.
+ *
+ * @throws ExecutionException if a subtask failed
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws IllegalStateException if the task scope owner did not join after forking
+ */
+ public void throwIfFailed() throws ExecutionException {
+ throwIfFailed(ExecutionException::new);
+ }
+
+ /**
+ * Throws the exception produced by the given exception supplying function if a
+ * subtask {@linkplain Subtask.State#FAILED failed}. If any subtask failed with
+ * an exception then the function is invoked with the exception of the first
+ * subtask to fail. The exception returned by the function is thrown. This method
+ * does nothing if no subtasks failed.
+ *
+ * @param esf the exception supplying function
+ * @param type of the exception to be thrown
+ *
+ * @throws X produced by the exception supplying function
+ * @throws WrongThreadException if the current thread is not the task scope owner
+ * @throws IllegalStateException if the task scope owner did not join after forking
+ */
+ public
+ void throwIfFailed(Function esf) throws X {
+ ensureOwnerAndJoined();
+ Objects.requireNonNull(esf);
+ Throwable exception = firstException;
+ if (exception != null) {
+ X ex = esf.apply(exception);
+ Objects.requireNonNull(ex, "esf returned null");
+ throw ex;
+ }
+ }
+ }
+}
diff --git a/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java b/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java
index 2577a282597..ca9ce08db74 100644
--- a/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java
+++ b/src/java.base/share/classes/java/util/concurrent/ThreadPerTaskExecutor.java
@@ -54,7 +54,7 @@ class ThreadPerTaskExecutor extends ThreadContainer implements ExecutorService {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(ThreadPerTaskExecutor.class, "state", int.class);
} catch (Exception e) {
- throw new InternalError(e);
+ throw new ExceptionInInitializerError(e);
}
}
diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
index b03ebb7bcd2..babc1940e78 100644
--- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
+++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java
@@ -531,15 +531,6 @@ public interface JavaLangAccess {
*/
Object scopedValueBindings();
- /**
- * Set the current thread's scoped value bindings.
- */
- void setScopedValueBindings(Object bindings);
-
- Object findScopedValueBindings();
-
- void ensureMaterializedForStackWalk(Object value);
-
/**
* Returns the innermost mounted continuation
*/
diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
index f7270427cea..2fee4f2fb64 100644
--- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
+++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java
@@ -74,6 +74,10 @@ public @interface PreviewFeature {
UNNAMED,
@JEP(number=445, title="Unnamed Classes and Instance Main Methods")
UNNAMED_CLASSES,
+ @JEP(number=446, title="Scoped Values", status="Preview")
+ SCOPED_VALUES,
+ @JEP(number=453, title="Structured Concurrency", status="Preview")
+ STRUCTURED_CONCURRENCY,
/**
* A key for testing.
*/
diff --git a/src/java.base/share/classes/jdk/internal/misc/StructureViolationExceptions.java b/src/java.base/share/classes/jdk/internal/misc/StructureViolationExceptions.java
deleted file mode 100644
index ad3d226f561..00000000000
--- a/src/java.base/share/classes/jdk/internal/misc/StructureViolationExceptions.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (c) 2022, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-package jdk.internal.misc;
-
-import java.lang.reflect.Constructor;
-
-/**
- * Defines static methods to allow code in java.base to create or throw
- * StructureViolationException. This class will go away when the exception
- * moves to java.base.
- */
-public class StructureViolationExceptions {
- private static final Constructor> SVE_CTOR = structureViolationExceptionCtor();
-
- private StructureViolationExceptions() { }
-
- /**
- * Creates a StructureViolationException with the given message.
- */
- public static RuntimeException newException(String message) {
- if (SVE_CTOR != null) {
- try {
- return (RuntimeException) SVE_CTOR.newInstance(message);
- } catch (Exception e) {
- throw new InternalError(e);
- }
- } else {
- return new RuntimeException("Structure violation exception: " + message);
- }
- }
-
- /**
- * Creates a StructureViolationException with no message.
- */
- public static RuntimeException newException() {
- return newException(null);
- }
-
- /**
- * Throws a StructureViolationException with the given message.
- */
- public static void throwException(String message) {
- throw newException(message);
- }
-
- /**
- * Throws a StructureViolationException with no message.
- */
- public static void throwException() {
- throw newException(null);
- }
-
- /**
- * Returns the StructureViolationException(String) constructor.
- */
- private static Constructor> structureViolationExceptionCtor() {
- Constructor> ctor;
- try {
- Class> exClass = Class.forName("jdk.incubator.concurrent.StructureViolationException");
- ctor = exClass.getConstructor(String.class);
- } catch (ClassNotFoundException e) {
- ctor = null;
- } catch (Exception e) {
- throw new InternalError(e);
- }
- return ctor;
- }
-}
diff --git a/src/java.base/share/classes/jdk/internal/misc/ThreadFlock.java b/src/java.base/share/classes/jdk/internal/misc/ThreadFlock.java
index 90e84d8ddfa..0b8cdfa7e48 100644
--- a/src/java.base/share/classes/jdk/internal/misc/ThreadFlock.java
+++ b/src/java.base/share/classes/jdk/internal/misc/ThreadFlock.java
@@ -31,6 +31,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.StructureViolationException;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Stream;
import jdk.internal.access.JavaLangAccess;
@@ -87,7 +88,6 @@ public class ThreadFlock implements AutoCloseable {
MethodHandles.Lookup l = MethodHandles.lookup();
THREAD_COUNT = l.findVarHandle(ThreadFlock.class, "threadCount", int.class);
PERMIT = l.findVarHandle(ThreadFlock.class, "permit", boolean.class);
- Unsafe.getUnsafe().ensureClassInitialized(StructureViolationExceptions.class);
} catch (Exception e) {
throw new InternalError(e);
}
@@ -262,8 +262,8 @@ public class ThreadFlock implements AutoCloseable {
* @throws IllegalThreadStateException if the given thread was already started
* @throws WrongThreadException if the current thread is not the owner or a thread
* contained in the flock
- * @throws jdk.incubator.concurrent.StructureViolationException if the current
- * scoped value bindings are not the same as when the flock was created
+ * @throws StructureViolationException if the current scoped value bindings are
+ * not the same as when the flock was created
*/
public Thread start(Thread thread) {
ensureOwnerOrContainsThread();
@@ -405,8 +405,7 @@ public class ThreadFlock implements AutoCloseable {
* thread flock.
*
* @throws WrongThreadException if invoked by a thread that is not the owner
- * @throws jdk.incubator.concurrent.StructureViolationException if a structure
- * violation was detected
+ * @throws StructureViolationException if a structure violation was detected
*/
public void close() {
ensureOwner();
@@ -513,14 +512,15 @@ public class ThreadFlock implements AutoCloseable {
@Override
public ThreadContainerImpl push() {
- // Virtual threads in the root containers are not tracked so need
+ // Virtual threads in the root containers may not be tracked so need
// to register container to ensure that it is found
- Thread thread = Thread.currentThread();
- if (thread.isVirtual()
- && JLA.threadContainer(thread) == ThreadContainers.root()) {
- this.key = ThreadContainers.registerContainer(this);
+ if (!ThreadContainers.trackAllThreads()) {
+ Thread thread = Thread.currentThread();
+ if (thread.isVirtual()
+ && JLA.threadContainer(thread) == ThreadContainers.root()) {
+ this.key = ThreadContainers.registerContainer(this);
+ }
}
-
super.push();
return this;
}
@@ -538,7 +538,7 @@ public class ThreadFlock implements AutoCloseable {
if (key != null)
ThreadContainers.deregisterContainer(key);
if (!atTop)
- StructureViolationExceptions.throwException();
+ throw new StructureViolationException();
}
}
@@ -563,6 +563,10 @@ public class ThreadFlock implements AutoCloseable {
}
}
+ @Override
+ public String name() {
+ return flock.name();
+ }
@Override
public long threadCount() {
return flock.threadCount();
@@ -580,10 +584,6 @@ public class ThreadFlock implements AutoCloseable {
flock.onExit(thread);
}
@Override
- public String toString() {
- return flock.toString();
- }
- @Override
public ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
return flock.scopedValueBindings();
}
diff --git a/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java b/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java
index e798d7e95e1..343c308660d 100644
--- a/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java
+++ b/src/java.base/share/classes/jdk/internal/vm/ScopedValueContainer.java
@@ -25,12 +25,10 @@
package jdk.internal.vm;
import java.util.concurrent.Callable;
+import java.util.concurrent.StructureViolationException;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
-import jdk.internal.misc.StructureViolationExceptions;
import jdk.internal.misc.Unsafe;
-import jdk.internal.vm.annotation.DontInline;
-import jdk.internal.vm.annotation.ReservedStackAccess;
/**
* A StackableScope to represent scoped-value bindings.
@@ -42,7 +40,7 @@ import jdk.internal.vm.annotation.ReservedStackAccess;
public class ScopedValueContainer extends StackableScope {
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
static {
- Unsafe.getUnsafe().ensureClassInitialized(StructureViolationExceptions.class);
+ Unsafe.getUnsafe().ensureClassInitialized(StructureViolationException.class);
}
private ScopedValueContainer() {
@@ -143,7 +141,7 @@ public class ScopedValueContainer extends StackableScope {
/**
* For use by ScopedValue to call a value returning operation in a structured context.
*/
- public static V call(Callable op) throws Exception {
+ public static V call(Callable op) {
if (head() == null) {
// no need to push scope when stack is empty
return callWithoutScope(op);
@@ -202,7 +200,7 @@ public class ScopedValueContainer extends StackableScope {
private static void throwIfFailed(Throwable ex, boolean atTop) {
if (ex != null || !atTop) {
if (!atTop) {
- var sve = StructureViolationExceptions.newException();
+ var sve = new StructureViolationException();
if (ex == null) {
ex = sve;
} else {
diff --git a/src/java.base/share/classes/jdk/internal/vm/SharedThreadContainer.java b/src/java.base/share/classes/jdk/internal/vm/SharedThreadContainer.java
index 586f6a0b2ba..36933ad6769 100644
--- a/src/java.base/share/classes/jdk/internal/vm/SharedThreadContainer.java
+++ b/src/java.base/share/classes/jdk/internal/vm/SharedThreadContainer.java
@@ -26,7 +26,6 @@ package jdk.internal.vm;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
@@ -95,6 +94,11 @@ public class SharedThreadContainer extends ThreadContainer implements AutoClosea
return create(ThreadContainers.root(), name);
}
+ @Override
+ public String name() {
+ return name;
+ }
+
@Override
public Thread owner() {
return null;
@@ -159,14 +163,4 @@ public class SharedThreadContainer extends ThreadContainer implements AutoClosea
ThreadContainers.deregisterContainer(key);
}
}
-
- @Override
- public String toString() {
- String id = Objects.toIdentityString(this);
- if (name != null) {
- return name + "/" + id;
- } else {
- return id;
- }
- }
}
diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadContainer.java b/src/java.base/share/classes/jdk/internal/vm/ThreadContainer.java
index 45bd013ecab..98a6c28496a 100644
--- a/src/java.base/share/classes/jdk/internal/vm/ThreadContainer.java
+++ b/src/java.base/share/classes/jdk/internal/vm/ThreadContainer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 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
@@ -25,6 +25,7 @@
package jdk.internal.vm;
+import java.util.Objects;
import java.util.stream.Stream;
/**
@@ -41,6 +42,13 @@ public abstract class ThreadContainer extends StackableScope {
super(shared);
}
+ /**
+ * Return the name of this container, may be null.
+ */
+ public String name() {
+ return null;
+ }
+
/**
* Returns the parent of this container or null if this is the root container.
*/
@@ -94,4 +102,15 @@ public abstract class ThreadContainer extends StackableScope {
public ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
return null;
}
+
+ @Override
+ public String toString() {
+ String name = name();
+ if (name != null && name.indexOf('@') >= 0) {
+ return name;
+ } else {
+ String id = Objects.toIdentityString(this);
+ return (name != null) ? name + "/" + id : id;
+ }
+ }
}
diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadContainers.java b/src/java.base/share/classes/jdk/internal/vm/ThreadContainers.java
index bb9cd4513d9..4a342fe2855 100644
--- a/src/java.base/share/classes/jdk/internal/vm/ThreadContainers.java
+++ b/src/java.base/share/classes/jdk/internal/vm/ThreadContainers.java
@@ -217,13 +217,17 @@ public class ThreadContainers {
return null;
}
@Override
- public String toString() {
+ public String name() {
return "";
}
@Override
public StackableScope previous() {
return null;
}
+ @Override
+ public String toString() {
+ return name();
+ }
/**
* Returns the platform threads that are not in the container as these
diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java
index cb2c873113a..06b141dcf22 100644
--- a/src/java.base/share/classes/module-info.java
+++ b/src/java.base/share/classes/module-info.java
@@ -162,7 +162,6 @@ module java.base {
jdk.jlink,
jdk.jfr,
jdk.net,
- jdk.incubator.concurrent,
jdk.sctp,
jdk.crypto.cryptoki;
exports jdk.internal.foreign to
@@ -222,7 +221,6 @@ module java.base {
jdk.charsets,
jdk.compiler,
jdk.crypto.cryptoki,
- jdk.incubator.concurrent,
jdk.incubator.vector,
jdk.jfr,
jdk.jshell,
@@ -256,7 +254,6 @@ module java.base {
jdk.unsupported;
exports jdk.internal.vm to
java.management,
- jdk.incubator.concurrent,
jdk.internal.jvmstat,
jdk.management,
jdk.management.agent,
@@ -264,7 +261,6 @@ module java.base {
exports jdk.internal.vm.annotation to
java.instrument,
jdk.internal.vm.ci,
- jdk.incubator.concurrent,
jdk.incubator.vector,
jdk.jfr,
jdk.unsupported;
@@ -326,8 +322,7 @@ module java.base {
exports sun.security.action to
java.desktop,
java.security.jgss,
- jdk.crypto.ec,
- jdk.incubator.concurrent;
+ jdk.crypto.ec;
exports sun.security.internal.interfaces to
jdk.crypto.cryptoki;
exports sun.security.internal.spec to
diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java
deleted file mode 100644
index 9e6c66b8695..00000000000
--- a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/StructuredTaskScope.java
+++ /dev/null
@@ -1,1223 +0,0 @@
-/*
- * Copyright (c) 2021, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-package jdk.incubator.concurrent;
-
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.VarHandle;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Comparator;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Function;
-import jdk.internal.misc.ThreadFlock;
-
-/**
- * A basic API for structured concurrency . {@code StructuredTaskScope} supports
- * cases where a task splits into several concurrent subtasks, to be executed in their
- * own threads, and where the subtasks must complete before the main task continues. A
- * {@code StructuredTaskScope} can be used to ensure that the lifetime of a concurrent
- * operation is confined by a syntax block , just like that of a sequential
- * operation in structured programming.
- *
- * Basic usage
- *
- * A {@code StructuredTaskScope} is created with one of its public constructors. It defines
- * the {@link #fork(Callable) fork} method to start a thread to execute a task, the {@link
- * #join() join} method to wait for all threads to finish, and the {@link #close() close}
- * method to close the task scope. The API is intended to be used with the {@code
- * try-with-resources} construct. The intention is that code in the block uses
- * the {@code fork} method to fork threads to execute the subtasks, wait for the threads
- * to finish with the {@code join} method, and then process the results .
- * Processing of results may include handling or re-throwing of exceptions.
- * {@snippet lang=java :
- * try (var scope = new StructuredTaskScope()) {
- *
- * Future future1 = scope.fork(task1); // @highlight substring="fork"
- * Future future2 = scope.fork(task2); // @highlight substring="fork"
- *
- * scope.join(); // @highlight substring="join"
- *
- * ... process results/exceptions ...
- *
- * } // close // @highlight substring="close"
- * }
- * To ensure correct usage, the {@code join} and {@code close} methods may only be invoked
- * by the owner (the thread that opened/created the task scope}, and the
- * {@code close} method throws an exception after closing if the owner did not invoke the
- * {@code join} method after forking.
- *
- * {@code StructuredTaskScope} defines the {@link #shutdown() shutdown} method to shut
- * down a task scope without closing it. Shutdown is useful for cases where a subtask
- * completes with a result (or exception) and the results of other unfinished subtasks are
- * no longer needed. If a subtask invokes {@code shutdown} while the owner is waiting in
- * the {@code join} method then it will cause {@code join} to wakeup, all unfinished
- * threads to be {@linkplain Thread#interrupt() interrupted} and prevents new threads
- * from starting in the task scope.
- *
- *
Subclasses with policies for common cases
- *
- * Two subclasses of {@code StructuredTaskScope} are defined to implement policy for
- * common cases:
- *
- * {@link ShutdownOnSuccess ShutdownOnSuccess} captures the first result and
- * shuts down the task scope to interrupt unfinished threads and wakeup the owner. This
- * class is intended for cases where the result of any subtask will do ("invoke any")
- * and where there is no need to wait for results of other unfinished tasks. It defines
- * methods to get the first result or throw an exception if all subtasks fail.
- * {@link ShutdownOnFailure ShutdownOnFailure} captures the first exception and
- * shuts down the task scope. This class is intended for cases where the results of all
- * subtasks are required ("invoke all"); if any subtask fails then the results of other
- * unfinished subtasks are no longer needed. If defines methods to throw an exception if
- * any of the subtasks fail.
- *
- *
- * The following are two examples that use the two classes. In both cases, a pair of
- * subtasks are forked to fetch resources from two URL locations "left" and "right". The
- * first example creates a ShutdownOnSuccess object to capture the result of the first
- * subtask to complete normally, cancelling the other by way of shutting down the task
- * scope. The main task waits in {@code join} until either subtask completes with a result
- * or both subtasks fail. It invokes {@link ShutdownOnSuccess#result(Function)
- * result(Function)} method to get the captured result. If both subtasks fail then this
- * method throws a {@code WebApplicationException} with the exception from one of the
- * subtasks as the cause.
- * {@snippet lang=java :
- * try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
- *
- * scope.fork(() -> fetch(left));
- * scope.fork(() -> fetch(right));
- *
- * scope.join();
- *
- * // @link regex="result(?=\()" target="ShutdownOnSuccess#result" :
- * String result = scope.result(e -> new WebApplicationException(e));
- *
- * ...
- * }
- * }
- * The second example creates a ShutdownOnFailure object to capture the exception of the
- * first subtask to fail, cancelling the other by way of shutting down the task scope. The
- * main task waits in {@link #joinUntil(Instant)} until both subtasks complete with a
- * result, either fails, or a deadline is reached. It invokes {@link
- * ShutdownOnFailure#throwIfFailed(Function) throwIfFailed(Function)} to throw an exception
- * when either subtask fails. This method is a no-op if no subtasks fail. The main task
- * uses {@code Future}'s {@link Future#resultNow() resultNow()} method to retrieve the
- * results.
- *
- * {@snippet lang=java :
- * Instant deadline = ...
- *
- * try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
- *
- * Future future1 = scope.fork(() -> query(left));
- * Future future2 = scope.fork(() -> query(right));
- *
- * scope.joinUntil(deadline);
- *
- * // @link substring="throwIfFailed" target="ShutdownOnFailure#throwIfFailed" :
- * scope.throwIfFailed(e -> new WebApplicationException(e));
- *
- * // both subtasks completed successfully
- * String result = Stream.of(future1, future2)
- * // @link substring="Future::resultNow" target="Future#resultNow" :
- * .map(Future::resultNow)
- * .collect(Collectors.joining(", ", "{ ", " }"));
- *
- * ...
- * }
- * }
- *
- * Extending StructuredTaskScope
- *
- * {@code StructuredTaskScope} can be extended, and the {@link #handleComplete(Future)
- * handleComplete} overridden, to implement policies other than those implemented by
- * {@code ShutdownOnSuccess} and {@code ShutdownOnFailure}. The method may be overridden
- * to, for example, collect the results of subtasks that complete with a result and ignore
- * subtasks that fail. It may collect exceptions when subtasks fail. It may invoke the
- * {@link #shutdown() shutdown} method to shut down and cause {@link #join() join} to
- * wakeup when some condition arises.
- *
- * A subclass will typically define methods to make available results, state, or other
- * outcome to code that executes after the {@code join} method. A subclass that collects
- * results and ignores subtasks that fail may define a method that returns a collection of
- * results. A subclass that implements a policy to shut down when a subtask fails may
- * define a method to retrieve the exception of the first subtask to fail.
- *
- *
The following is an example of a {@code StructuredTaskScope} implementation that
- * collects the results of subtasks that complete successfully. It defines the method
- * {@code results()} to be used by the main task to retrieve the results.
- *
- * {@snippet lang=java :
- * class MyScope extends StructuredTaskScope {
- * private final Queue results = new ConcurrentLinkedQueue<>();
- *
- * MyScope() {
- * super(null, Thread.ofVirtual().factory());
- * }
- *
- * @Override
- * // @link substring="handleComplete" target="handleComplete" :
- * protected void handleComplete(Future future) {
- * if (future.state() == Future.State.SUCCESS) {
- * T result = future.resultNow();
- * results.add(result);
- * }
- * }
- *
- * // Returns a stream of results from the subtasks that completed successfully
- * public Stream results() { // @highlight substring="results"
- * return results.stream();
- * }
- * }
- * }
- *
- *
- *
- * Task scopes form a tree where parent-child relations are established implicitly when
- * opening a new task scope:
- *
- * A parent-child relation is established when a thread started in a task scope
- * opens its own task scope. A thread started in task scope "A" that opens task scope
- * "B" establishes a parent-child relation where task scope "A" is the parent of task
- * scope "B".
- * A parent-child relation is established with nesting. If a thread opens task
- * scope "B", then opens task scope "C" (before it closes "B"), then the enclosing task
- * scope "B" is the parent of the nested task scope "C".
- *
- *
- * The descendants of a task scope are the child task scopes that it is a parent
- * of, plus the descendants of the child task scopes, recursively.
- *
- * The tree structure supports:
- *
- * Inheritance of {@linkplain ScopedValue scoped values} across threads.
- * Confinement checks. The phrase "threads contained in the task scope" in method
- * descriptions means threads started in the task scope or descendant scopes.
- *
- *
- * The following example demonstrates the inheritance of a scoped value. A scoped
- * value {@code USERNAME} is bound to the value "{@code duke}". A {@code StructuredTaskScope}
- * is created and its {@code fork} method invoked to start a thread to execute {@code
- * childTask}. The thread inherits the scoped value bindings captured when
- * creating the task scope. The code in {@code childTask} uses the value of the scoped
- * value and so reads the value "{@code duke}".
- * {@snippet lang=java :
- * private static final ScopedValue USERNAME = ScopedValue.newInstance();
- *
- * // @link substring="where" target="ScopedValue#where(ScopedValue, Object, Runnable)" :
- * ScopedValue.where(USERNAME, "duke", () -> {
- * try (var scope = new StructuredTaskScope()) {
- *
- * scope.fork(() -> childTask()); // @highlight substring="fork"
- * ...
- * }
- * });
- *
- * ...
- *
- * String childTask() {
- * // @link substring="get" target="ScopedValue#get()" :
- * String name = USERNAME.get(); // "duke"
- * ...
- * }
- * }
- *
- * {@code StructuredTaskScope} does not define APIs that exposes the tree structure
- * at this time.
- *
- *
Unless otherwise specified, passing a {@code null} argument to a constructor
- * or method in this class will cause a {@link NullPointerException} to be thrown.
- *
- *
Memory consistency effects
- *
- * Actions in the owner thread of, or a thread contained in, the task scope prior to
- * {@linkplain #fork forking} of a {@code Callable} task
- *
- * happen-before any actions taken by that task, which in turn happen-before
- * the task result is retrieved via its {@code Future}, or happen-before any actions
- * taken in a thread after {@linkplain #join() joining} of the task scope.
- *
- * @jls 17.4.5 Happens-before Order
- *
- * @param the result type of tasks executed in the scope
- * @since 19
- */
-public class StructuredTaskScope implements AutoCloseable {
- private static final VarHandle FUTURES;
- static {
- try {
- MethodHandles.Lookup l = MethodHandles.lookup();
- FUTURES = l.findVarHandle(StructuredTaskScope.class, "futures", Set.class);
- } catch (Exception e) {
- throw new InternalError(e);
- }
- }
-
- private final ThreadFactory factory;
- private final ThreadFlock flock;
- private final ReentrantLock shutdownLock = new ReentrantLock();
-
- // lazily created set of Future objects with threads waiting in Future::get
- private volatile Set> futures;
-
- // set by owner when it forks, reset by owner when it joins
- private boolean needJoin;
-
- // states: OPEN -> SHUTDOWN -> CLOSED
- private static final int OPEN = 0; // initial state
- private static final int SHUTDOWN = 1;
- private static final int CLOSED = 2;
-
- // scope state, set by owner, read by any thread
- private volatile int state;
-
- /**
- * Creates a structured task scope with the given name and thread factory. The task
- * scope is optionally named for the purposes of monitoring and management. The thread
- * factory is used to {@link ThreadFactory#newThread(Runnable) create} threads when
- * tasks are {@linkplain #fork(Callable) forked}. The task scope is owned by the
- * current thread.
- *
- * This method captures the current thread's {@linkplain ScopedValue scoped value}
- * bindings for inheritance by threads created in the task scope. The
- * Tree Structure section in the class description
- * details how parent-child relations are established implicitly for the purpose of
- * inheritance of scoped value bindings.
- *
- * @param name the name of the task scope, can be null
- * @param factory the thread factory
- */
- public StructuredTaskScope(String name, ThreadFactory factory) {
- this.factory = Objects.requireNonNull(factory, "'factory' is null");
- this.flock = ThreadFlock.open(name);
- }
-
- /**
- * Creates an unnamed structured task scope that creates virtual threads. The task
- * scope is owned by the current thread.
- *
- *
This constructor is equivalent to invoking the 2-arg constructor with a name
- * of {@code null} and a thread factory that creates virtual threads.
- */
- public StructuredTaskScope() {
- this.factory = Thread.ofVirtual().factory();
- this.flock = ThreadFlock.open(null);
- }
-
- /**
- * Throws WrongThreadException if the current thread is not the owner.
- */
- private void ensureOwner() {
- if (Thread.currentThread() != flock.owner())
- throw new WrongThreadException("Current thread not owner");
- }
-
- /**
- * Throws WrongThreadException if the current thread is not the owner
- * or a thread contained in the tree.
- */
- private void ensureOwnerOrContainsThread() {
- Thread currentThread = Thread.currentThread();
- if (currentThread != flock.owner() && !flock.containsThread(currentThread))
- throw new WrongThreadException("Current thread not owner or thread in the tree");
- }
-
- /**
- * Tests if the task scope is shutdown.
- */
- private boolean isShutdown() {
- return state >= SHUTDOWN;
- }
-
- /**
- * Track the given Future.
- */
- private void track(Future> future) {
- // create the set of Futures if not already created
- Set> futures = this.futures;
- if (futures == null) {
- futures = ConcurrentHashMap.newKeySet();
- if (!FUTURES.compareAndSet(this, null, futures)) {
- // lost the race
- futures = this.futures;
- }
- }
- futures.add(future);
- }
-
- /**
- * Stop tracking the Future.
- */
- private void untrack(Future> future) {
- assert futures != null;
- futures.remove(future);
- }
-
- /**
- * Invoked when a task completes before the scope is shut down.
- *
- * The {@code handleComplete} method should be thread safe. It may be invoked by
- * several threads concurrently.
- *
- * @implSpec The default implementation does nothing.
- *
- * @param future the completed task
- */
- protected void handleComplete(Future future) { }
-
- /**
- * Starts a new thread to run the given task.
- *
- * The new thread is created with the task scope's {@link ThreadFactory}. It
- * inherits the current thread's {@linkplain ScopedValue scoped value} bindings. The
- * bindings must match the bindings captured when the task scope was created.
- *
- *
If the task completes before the task scope is {@link #shutdown() shutdown}
- * then the {@link #handleComplete(Future) handleComplete} method is invoked to
- * consume the completed task. The {@code handleComplete} method is run when the task
- * completes with a result or exception. If the {@code Future}'s {@link
- * Future#cancel(boolean) cancel} method is used to cancel a task before the task scope
- * is shut down, then the {@code handleComplete} method is run by the thread that
- * invokes {@code cancel}. If the task scope shuts down at or around the same time
- * that the task completes or is cancelled then the {@code handleComplete} method may
- * or may not be invoked.
- *
- *
If this task scope is {@linkplain #shutdown() shutdown} (or in the process
- * of shutting down) then {@code fork} returns a {@code Future} representing a {@link
- * Future.State#CANCELLED cancelled} task that was not run.
- *
- *
This method may only be invoked by the task scope owner or threads contained
- * in the task scope. The {@link Future#cancel(boolean) cancel} method of the returned
- * {@code Future} object is also restricted to the task scope owner or threads contained
- * in the task scope. The {@code cancel} method throws {@link WrongThreadException}
- * if invoked from another thread. All other methods on the returned {@code Future}
- * object, such as {@link Future#get() get}, are not restricted.
- *
- * @param task the task to run
- * @param the result type
- * @return a future
- * @throws IllegalStateException if this task scope is closed
- * @throws WrongThreadException if the current thread is not the owner or a thread
- * contained in the task scope
- * @throws StructureViolationException if the current scoped value bindings are not
- * the same as when the task scope was created
- * @throws RejectedExecutionException if the thread factory rejected creating a
- * thread to run the task
- */
- public Future fork(Callable extends U> task) {
- Objects.requireNonNull(task, "'task' is null");
-
- // create future
- var future = new FutureImpl(this, task);
-
- boolean shutdown = (state >= SHUTDOWN);
-
- if (!shutdown) {
- // create thread
- Thread thread = factory.newThread(future);
- if (thread == null) {
- throw new RejectedExecutionException("Rejected by thread factory");
- }
-
- // attempt to start the thread
- try {
- flock.start(thread);
- } catch (IllegalStateException e) {
- // shutdown or in the process of shutting down
- shutdown = true;
- }
- }
-
- if (shutdown) {
- if (state == CLOSED) {
- throw new IllegalStateException("Task scope is closed");
- } else {
- future.cancel(false);
- }
- }
-
- // if owner forks then it will need to join
- if (Thread.currentThread() == flock.owner() && !needJoin) {
- needJoin = true;
- }
-
- return future;
- }
-
- /**
- * Wait for all threads to finish or the task scope to shut down.
- */
- private void implJoin(Duration timeout)
- throws InterruptedException, TimeoutException
- {
- ensureOwner();
- needJoin = false;
- int s = state;
- if (s >= SHUTDOWN) {
- if (s == CLOSED)
- throw new IllegalStateException("Task scope is closed");
- return;
- }
-
- // wait for all threads, wakeup, interrupt, or timeout
- if (timeout != null) {
- flock.awaitAll(timeout);
- } else {
- flock.awaitAll();
- }
- }
-
- /**
- * Wait for all threads to finish or the task scope to shut down. This method waits
- * until all threads started in the task scope finish execution (of both task and
- * {@link #handleComplete(Future) handleComplete} method), the {@link #shutdown()
- * shutdown} method is invoked to shut down the task scope, or the current thread
- * is {@linkplain Thread#interrupt() interrupted}.
- *
- *
This method may only be invoked by the task scope owner.
- *
- * @return this task scope
- * @throws IllegalStateException if this task scope is closed
- * @throws WrongThreadException if the current thread is not the owner
- * @throws InterruptedException if interrupted while waiting
- */
- public StructuredTaskScope join() throws InterruptedException {
- try {
- implJoin(null);
- } catch (TimeoutException e) {
- throw new InternalError();
- }
- return this;
- }
-
- /**
- * Wait for all threads to finish or the task scope to shut down, up to the given
- * deadline. This method waits until all threads started in the task scope finish
- * execution (of both task and {@link #handleComplete(Future) handleComplete} method),
- * the {@link #shutdown() shutdown} method is invoked to shut down the task scope,
- * the current thread is {@linkplain Thread#interrupt() interrupted}, or the deadline
- * is reached.
- *
- * This method may only be invoked by the task scope owner.
- *
- * @param deadline the deadline
- * @return this task scope
- * @throws IllegalStateException if this task scope is closed
- * @throws WrongThreadException if the current thread is not the owner
- * @throws InterruptedException if interrupted while waiting
- * @throws TimeoutException if the deadline is reached while waiting
- */
- public StructuredTaskScope joinUntil(Instant deadline)
- throws InterruptedException, TimeoutException
- {
- Duration timeout = Duration.between(Instant.now(), deadline);
- implJoin(timeout);
- return this;
- }
-
- /**
- * Cancel all tracked Future objects.
- */
- private void cancelTrackedFutures() {
- Set> futures = this.futures;
- if (futures != null) {
- futures.forEach(f -> f.cancel(false));
- }
- }
-
- /**
- * Interrupt all unfinished threads.
- */
- private void implInterruptAll() {
- flock.threads().forEach(t -> {
- if (t != Thread.currentThread()) {
- t.interrupt();
- }
- });
- }
-
- @SuppressWarnings("removal")
- private void interruptAll() {
- if (System.getSecurityManager() == null) {
- implInterruptAll();
- } else {
- PrivilegedAction pa = () -> {
- implInterruptAll();
- return null;
- };
- AccessController.doPrivileged(pa);
- }
- }
-
- /**
- * Shutdown the task scope if not already shutdown. Return true if this method
- * shutdowns the task scope, false if already shutdown.
- */
- private boolean implShutdown() {
- if (state < SHUTDOWN) {
- shutdownLock.lock();
- try {
- if (state < SHUTDOWN) {
-
- // prevent new threads from starting
- flock.shutdown();
-
- // wakeup any threads waiting in Future::get
- cancelTrackedFutures();
-
- // interrupt all unfinished threads
- interruptAll();
-
- state = SHUTDOWN;
- return true;
- }
- } finally {
- shutdownLock.unlock();
- }
- }
- assert state >= SHUTDOWN;
- return false;
- }
-
- /**
- * Shut down the task scope without closing it. Shutting down a task scope prevents
- * new threads from starting, interrupts all unfinished threads, and causes the
- * {@link #join() join} method to wakeup. Shutdown is useful for cases where the
- * results of unfinished subtasks are no longer needed.
- *
- * More specifically, this method:
- *
- * {@linkplain Future#cancel(boolean) Cancels} the tasks that have threads
- * {@linkplain Future#get() waiting} on a result so that the waiting threads wakeup.
- * {@linkplain Thread#interrupt() Interrupts} all unfinished threads in the
- * task scope (except the current thread).
- * Wakes up the owner if it is waiting in {@link #join()} or {@link
- * #joinUntil(Instant)}. If the owner is not waiting then its next call to {@code
- * join} or {@code joinUntil} will return immediately.
- *
- *
- * When this method completes then the {@code Future} objects for all tasks will
- * be {@linkplain Future#isDone() done}, normally or abnormally. There may still
- * be threads that have not finished because they are executing code that did not
- * respond (or respond promptly) to thread interrupt. This method does not wait
- * for these threads. When the owner invokes the {@link #close() close} method
- * to close the task scope then it will wait for the remaining threads to finish.
- *
- *
This method may only be invoked by the task scope owner or threads contained
- * in the task scope.
- *
- * @throws IllegalStateException if this task scope is closed
- * @throws WrongThreadException if the current thread is not the owner or
- * a thread contained in the task scope
- */
- public void shutdown() {
- ensureOwnerOrContainsThread();
- if (state == CLOSED)
- throw new IllegalStateException("Task scope is closed");
- if (implShutdown())
- flock.wakeup();
- }
-
- /**
- * Closes this task scope.
- *
- *
This method first shuts down the task scope (as if by invoking the {@link
- * #shutdown() shutdown} method). It then waits for the threads executing any
- * unfinished tasks to finish. If interrupted then this method will continue to
- * wait for the threads to finish before completing with the interrupt status set.
- *
- *
This method may only be invoked by the task scope owner. If the task scope
- * is already closed then the owner invoking this method has no effect.
- *
- *
A {@code StructuredTaskScope} is intended to be used in a structured
- * manner . If this method is called to close a task scope before nested task
- * scopes are closed then it closes the underlying construct of each nested task scope
- * (in the reverse order that they were created in), closes this task scope, and then
- * throws {@link StructureViolationException}.
- *
- * Similarly, if this method is called to close a task scope while executing with
- * {@linkplain ScopedValue scoped value} bindings, and the task scope was created
- * before the scoped values were bound, then {@code StructureViolationException} is
- * thrown after closing the task scope.
- *
- * If a thread terminates without first closing task scopes that it owns then
- * termination will cause the underlying construct of each of its open tasks scopes to
- * be closed. Closing is performed in the reverse order that the task scopes were
- * created in. Thread termination may therefore be delayed when the owner has to wait
- * for threads forked in these task scopes to finish.
- *
- * @throws IllegalStateException thrown after closing the task scope if the owner
- * did not invoke join after forking
- * @throws WrongThreadException if the current thread is not the owner
- * @throws StructureViolationException if a structure violation was detected
- */
- @Override
- public void close() {
- ensureOwner();
- if (state == CLOSED)
- return;
-
- try {
- implShutdown();
- flock.close();
- } finally {
- state = CLOSED;
- }
-
- if (needJoin) {
- throw new IllegalStateException("Owner did not invoke join or joinUntil after fork");
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- String name = flock.name();
- if (name != null) {
- sb.append(name);
- sb.append('/');
- }
- sb.append(Objects.toIdentityString(this));
- int s = state;
- if (s == CLOSED)
- sb.append("/closed");
- else if (s == SHUTDOWN)
- sb.append("/shutdown");
- return sb.toString();
- }
-
- /**
- * The Future implementation returned by the fork methods. Most methods are
- * overridden to support cancellation when the task scope is shutdown.
- * The blocking get methods register the Future with the task scope so that they
- * are cancelled when the task scope shuts down.
- */
- private static final class FutureImpl extends FutureTask {
- private final StructuredTaskScope scope;
-
- @SuppressWarnings("unchecked")
- FutureImpl(StructuredTaskScope super V> scope, Callable extends V> task) {
- super((Callable) task);
- this.scope = (StructuredTaskScope) scope;
- }
-
- @Override
- protected void done() {
- if (!scope.isShutdown()) {
- scope.handleComplete(this);
- }
- }
-
- private void cancelIfShutdown() {
- if (scope.isShutdown() && !super.isDone()) {
- super.cancel(false);
- }
- }
-
- @Override
- public boolean isDone() {
- cancelIfShutdown();
- return super.isDone();
- }
-
- @Override
- public boolean isCancelled() {
- cancelIfShutdown();
- return super.isCancelled();
- }
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- scope.ensureOwnerOrContainsThread();
- cancelIfShutdown();
- return super.cancel(mayInterruptIfRunning);
- }
-
- @Override
- public V get() throws InterruptedException, ExecutionException {
- if (super.isDone())
- return super.get();
- scope.track(this);
- try {
- cancelIfShutdown();
- return super.get();
- } finally {
- scope.untrack(this);
- }
- }
-
- @Override
- public V get(long timeout, TimeUnit unit)
- throws InterruptedException, ExecutionException, TimeoutException {
- Objects.requireNonNull(unit);
- if (super.isDone())
- return super.get();
- scope.track(this);
- try {
- cancelIfShutdown();
- return super.get(timeout, unit);
- } finally {
- scope.untrack(this);
- }
- }
-
- @Override
- public V resultNow() {
- cancelIfShutdown();
- return super.resultNow();
- }
-
- @Override
- public Throwable exceptionNow() {
- cancelIfShutdown();
- return super.exceptionNow();
- }
-
- @Override
- public State state() {
- cancelIfShutdown();
- return super.state();
- }
-
- @Override
- public String toString() {
- cancelIfShutdown();
- return super.toString();
- }
- }
-
- /**
- * Maps a Future.State to an int that can be compared.
- * RUNNING < CANCELLED < FAILED < SUCCESS.
- */
- private static int futureStateToInt(Future.State s) {
- return switch (s) {
- case RUNNING -> 0;
- case CANCELLED -> 1;
- case FAILED -> 2;
- case SUCCESS -> 3;
- };
- }
-
- // RUNNING < CANCELLED < FAILED < SUCCESS
- private static final Comparator FUTURE_STATE_COMPARATOR =
- Comparator.comparingInt(StructuredTaskScope::futureStateToInt);
-
- /**
- * A {@code StructuredTaskScope} that captures the result of the first subtask to
- * complete successfully. Once captured, it invokes the {@linkplain #shutdown() shutdown}
- * method to interrupt unfinished threads and wakeup the owner. The policy
- * implemented by this class is intended for cases where the result of any subtask
- * will do ("invoke any") and where the results of other unfinished subtask are no
- * longer needed.
- *
- * Unless otherwise specified, passing a {@code null} argument to a method
- * in this class will cause a {@link NullPointerException} to be thrown.
- *
- * @param the result type
- * @since 19
- */
- public static final class ShutdownOnSuccess extends StructuredTaskScope {
- private static final VarHandle FUTURE;
- static {
- try {
- MethodHandles.Lookup l = MethodHandles.lookup();
- FUTURE = l.findVarHandle(ShutdownOnSuccess.class, "future", Future.class);
- } catch (Exception e) {
- throw new InternalError(e);
- }
- }
- private volatile Future future;
-
- /**
- * Constructs a new {@code ShutdownOnSuccess} with the given name and thread factory.
- * The task scope is optionally named for the purposes of monitoring and management.
- * The thread factory is used to {@link ThreadFactory#newThread(Runnable) create}
- * threads when tasks are {@linkplain #fork(Callable) forked}. The task scope is
- * owned by the current thread.
- *
- * This method captures the current thread's {@linkplain ScopedValue scoped value}
- * bindings for inheritance by threads created in the task scope. The
- * Tree Structure section in
- * the class description details how parent-child relations are established
- * implicitly for the purpose of inheritance of scoped value bindings.
- *
- * @param name the name of the task scope, can be null
- * @param factory the thread factory
- */
- public ShutdownOnSuccess(String name, ThreadFactory factory) {
- super(name, factory);
- }
-
- /**
- * Constructs a new unnamed {@code ShutdownOnSuccess} that creates virtual threads.
- *
- *
This constructor is equivalent to invoking the 2-arg constructor with a
- * name of {@code null} and a thread factory that creates virtual threads.
- */
- public ShutdownOnSuccess() {
- super(null, Thread.ofVirtual().factory());
- }
-
- /**
- * Shut down the given task scope when invoked for the first time with a {@code
- * Future} for a task that completed with a result.
- *
- * @param future the completed task
- * @see #shutdown()
- * @see Future.State#SUCCESS
- */
- @Override
- protected void handleComplete(Future future) {
- Future.State state = future.state();
- if (state == Future.State.RUNNING) {
- throw new IllegalArgumentException("Task is not completed");
- }
-
- Future f;
- while (((f = this.future) == null)
- || FUTURE_STATE_COMPARATOR.compare(f.state(), state) < 0) {
- if (FUTURE.compareAndSet(this, f, future)) {
- if (state == Future.State.SUCCESS)
- shutdown();
- break;
- }
- }
- }
-
- /**
- * {@inheritDoc}
- * @return this task scope
- * @throws IllegalStateException {@inheritDoc}
- * @throws WrongThreadException {@inheritDoc}
- */
- @Override
- public ShutdownOnSuccess join() throws InterruptedException {
- super.join();
- return this;
- }
-
- /**
- * {@inheritDoc}
- * @return this task scope
- * @throws IllegalStateException {@inheritDoc}
- * @throws WrongThreadException {@inheritDoc}
- */
- @Override
- public ShutdownOnSuccess joinUntil(Instant deadline)
- throws InterruptedException, TimeoutException
- {
- super.joinUntil(deadline);
- return this;
- }
-
- /**
- * {@return the result of the first subtask that completed with a result}
- *
- * When no subtask completed with a result but a task completed with an
- * exception then {@code ExecutionException} is thrown with the exception as the
- * {@linkplain Throwable#getCause() cause}. If only cancelled subtasks were
- * notified to the {@code handleComplete} method then {@code CancellationException}
- * is thrown.
- *
- * @apiNote This method is intended to be invoked by the task scope owner after it
- * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}).
- * A future release may add enforcement to prevent the method being called by
- * other threads or before joining.
- *
- * @throws ExecutionException if no subtasks completed with a result but a subtask
- * completed with an exception
- * @throws CancellationException if all subtasks were cancelled
- * @throws IllegalStateException if the handle method was not invoked with a
- * completed subtask
- */
- public T result() throws ExecutionException {
- Future f = future;
- if (f == null) {
- throw new IllegalStateException("No completed subtasks");
- }
- return switch (f.state()) {
- case SUCCESS -> f.resultNow();
- case FAILED -> throw new ExecutionException(f.exceptionNow());
- case CANCELLED -> throw new CancellationException();
- default -> throw new InternalError("Unexpected state: " + f);
- };
-
- }
-
- /**
- * Returns the result of the first subtask that completed with a result, otherwise
- * throws an exception produced by the given exception supplying function.
- *
- * When no subtask completed with a result but a subtask completed with an
- * exception then the exception supplying function is invoked with the exception.
- * If only cancelled subtasks were notified to the {@code handleComplete} method
- * then the exception supplying function is invoked with a {@code CancellationException}.
- *
- * @apiNote This method is intended to be invoked by the task scope owner after it
- * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}).
- * A future release may add enforcement to prevent the method being called by
- * other threads or before joining.
- *
- * @param esf the exception supplying function
- * @param type of the exception to be thrown
- * @return the result of the first subtask that completed with a result
- * @throws X if no subtask completed with a result
- * @throws IllegalStateException if the handle method was not invoked with a
- * completed subtask
- */
- public T result(Function esf) throws X {
- Objects.requireNonNull(esf);
- Future f = future;
- if (f == null) {
- throw new IllegalStateException("No completed subtasks");
- }
- Future.State state = f.state();
- if (state == Future.State.SUCCESS) {
- return f.resultNow();
- } else {
- Throwable throwable = (state == Future.State.FAILED)
- ? f.exceptionNow()
- : new CancellationException();
- X ex = esf.apply(throwable);
- Objects.requireNonNull(ex, "esf returned null");
- throw ex;
- }
- }
- }
-
- /**
- * A {@code StructuredTaskScope} that captures the exception of the first subtask to
- * complete abnormally. Once captured, it invokes the {@linkplain #shutdown() shutdown}
- * method to interrupt unfinished threads and wakeup the owner. The policy implemented
- * by this class is intended for cases where the results for all subtasks are required
- * ("invoke all"); if any subtask fails then the results of other unfinished subtasks
- * are no longer needed.
- *
- * Unless otherwise specified, passing a {@code null} argument to a method
- * in this class will cause a {@link NullPointerException} to be thrown.
- *
- * @since 19
- */
- public static final class ShutdownOnFailure extends StructuredTaskScope {
- private static final VarHandle FUTURE;
- static {
- try {
- MethodHandles.Lookup l = MethodHandles.lookup();
- FUTURE = l.findVarHandle(ShutdownOnFailure.class, "future", Future.class);
- } catch (Exception e) {
- throw new InternalError(e);
- }
- }
- private volatile Future future;
-
- /**
- * Constructs a new {@code ShutdownOnFailure} with the given name and thread factory.
- * The task scope is optionally named for the purposes of monitoring and management.
- * The thread factory is used to {@link ThreadFactory#newThread(Runnable) create}
- * threads when tasks are {@linkplain #fork(Callable) forked}. The task scope
- * is owned by the current thread.
- *
- * This method captures the current thread's {@linkplain ScopedValue scoped value}
- * bindings for inheritance by threads created in the task scope. The
- * Tree Structure section in
- * the class description details how parent-child relations are established
- * implicitly for the purpose of inheritance of scoped value bindings.
- *
- * @param name the name of the task scope, can be null
- * @param factory the thread factory
- */
- public ShutdownOnFailure(String name, ThreadFactory factory) {
- super(name, factory);
- }
-
- /**
- * Constructs a new unnamed {@code ShutdownOnFailure} that creates virtual threads.
- *
- *
This constructor is equivalent to invoking the 2-arg constructor with a
- * name of {@code null} and a thread factory that creates virtual threads.
- */
- public ShutdownOnFailure() {
- super(null, Thread.ofVirtual().factory());
- }
-
- /**
- * Shut down the given task scope when invoked for the first time with a {@code
- * Future} for a task that completed abnormally (exception or cancelled).
- *
- * @param future the completed task
- * @see #shutdown()
- * @see Future.State#FAILED
- * @see Future.State#CANCELLED
- */
- @Override
- protected void handleComplete(Future future) {
- Future.State state = future.state();
- if (state == Future.State.RUNNING) {
- throw new IllegalArgumentException("Task is not completed");
- } else if (state == Future.State.SUCCESS) {
- return;
- }
-
- // A failed task overrides a cancelled task.
- // The first failed or cancelled task causes the scope to shutdown.
- Future f;
- while (((f = this.future) == null)
- || FUTURE_STATE_COMPARATOR.compare(f.state(), state) < 0) {
- if (FUTURE.compareAndSet(this, f, future)) {
- shutdown();
- break;
- }
- }
- }
-
- /**
- * {@inheritDoc}
- * @return this task scope
- * @throws IllegalStateException {@inheritDoc}
- * @throws WrongThreadException {@inheritDoc}
- */
- @Override
- public ShutdownOnFailure join() throws InterruptedException {
- super.join();
- return this;
- }
-
- /**
- * {@inheritDoc}
- * @return this task scope
- * @throws IllegalStateException {@inheritDoc}
- * @throws WrongThreadException {@inheritDoc}
- */
- @Override
- public ShutdownOnFailure joinUntil(Instant deadline)
- throws InterruptedException, TimeoutException
- {
- super.joinUntil(deadline);
- return this;
- }
-
- /**
- * Returns the exception for the first subtask that completed with an exception.
- * If no subtask completed with an exception but cancelled subtasks were notified
- * to the {@code handleComplete} method then a {@code CancellationException}
- * is returned. If no subtasks completed abnormally then an empty {@code Optional}
- * is returned.
- *
- * @apiNote This method is intended to be invoked by the task scope owner after it
- * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}).
- * A future release may add enforcement to prevent the method being called by
- * other threads or before joining.
- *
- * @return the exception for a subtask that completed abnormally or an empty
- * optional if no subtasks completed abnormally
- */
- public Optional exception() {
- Future f = future;
- if (f != null) {
- Throwable throwable = (f.state() == Future.State.FAILED)
- ? f.exceptionNow()
- : new CancellationException();
- return Optional.of(throwable);
- } else {
- return Optional.empty();
- }
- }
-
- /**
- * Throws if a subtask completed abnormally. If any subtask completed with an
- * exception then {@code ExecutionException} is thrown with the exception of the
- * first subtask to fail as the {@linkplain Throwable#getCause() cause}. If no
- * subtask completed with an exception but cancelled subtasks were notified to the
- * {@code handleComplete} method then {@code CancellationException} is thrown.
- * This method does nothing if no subtasks completed abnormally.
- *
- * @apiNote This method is intended to be invoked by the task scope owner after it
- * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}).
- * A future release may add enforcement to prevent the method being called by
- * other threads or before joining.
- *
- * @throws ExecutionException if a subtask completed with an exception
- * @throws CancellationException if no subtasks completed with an exception but
- * subtasks were cancelled
- */
- public void throwIfFailed() throws ExecutionException {
- Future f = future;
- if (f != null) {
- if (f.state() == Future.State.FAILED) {
- throw new ExecutionException(f.exceptionNow());
- } else {
- throw new CancellationException();
- }
- }
- }
-
- /**
- * Throws the exception produced by the given exception supplying function if
- * a subtask completed abnormally. If any subtask completed with an exception then
- * the function is invoked with the exception of the first subtask to fail.
- * If no subtask completed with an exception but cancelled subtasks were notified
- * to the {@code handleComplete} method then the function is called with a {@code
- * CancellationException}. The exception returned by the function is thrown.
- * This method does nothing if no subtasks completed abnormally.
- *
- * @apiNote This method is intended to be invoked by the task scope owner after it
- * has invoked {@link #join() join} (or {@link #joinUntil(Instant) joinUntil}).
- * A future release may add enforcement to prevent the method being called by
- * other threads or before joining.
- *
- * @param esf the exception supplying function
- * @param type of the exception to be thrown
- * @throws X produced by the exception supplying function
- */
- public
- void throwIfFailed(Function esf) throws X {
- Objects.requireNonNull(esf);
- Future f = future;
- if (f != null) {
- Throwable throwable = (f.state() == Future.State.FAILED)
- ? f.exceptionNow()
- : new CancellationException();
- X ex = esf.apply(throwable);
- Objects.requireNonNull(ex, "esf returned null");
- throw ex;
- }
- }
- }
-}
diff --git a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java b/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java
deleted file mode 100644
index b51a8c12a3b..00000000000
--- a/src/jdk.incubator.concurrent/share/classes/jdk/incubator/concurrent/package-info.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2022, 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-/**
- * Defines non-final APIs for concurrent programming.
- * {@Incubating}
- */
-package jdk.incubator.concurrent;
diff --git a/src/jdk.incubator.concurrent/share/classes/module-info.java b/src/jdk.incubator.concurrent/share/classes/module-info.java
deleted file mode 100644
index 857c6125f96..00000000000
--- a/src/jdk.incubator.concurrent/share/classes/module-info.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-/**
- * Defines non-final APIs for concurrent programming.
- * {@Incubating}
- *
- * @moduleGraph
- */
-module jdk.incubator.concurrent {
- exports jdk.incubator.concurrent;
-}
-
diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
index 634d47e2410..1d6c448ad0a 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -487,6 +487,8 @@ java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic-
java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-x64
java/lang/invoke/RicochetTest.java 8251969 generic-all
+java/lang/ScopedValue/StressStackOverflow.java 8303498 linux-s390x
+
############################################################################
# jdk_instrument
@@ -739,9 +741,6 @@ sun/tools/jhsdb/JStackStressTest.java 8276210 linux-aa
javax/rmi/ssl/SSLSocketParametersTest.sh 8162906 generic-all
-
-jdk/incubator/concurrent/ScopedValue/StressStackOverflow.java 8303498 linux-s390x
-
jdk/incubator/vector/ShortMaxVectorTests.java 8306592 generic-i586
jdk/incubator/vector/LoadJsvmlTest.java 8305390 windows-x64
diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups
index f3f352d4e1e..00e040c41d5 100644
--- a/test/jdk/TEST.groups
+++ b/test/jdk/TEST.groups
@@ -279,7 +279,6 @@ jdk_loom = \
java/util/concurrent \
java/net/vthread \
java/nio/channels/vthread \
- jdk/incubator/concurrent \
jdk/internal/misc/ThreadFlock \
jdk/internal/vm/Continuation \
jdk/jfr/threading
@@ -319,7 +318,6 @@ jdk_other = \
javax/xml \
-javax/xml/crypto \
jdk/dynalink \
- jdk/incubator/concurrent \
jdk/internal/jline \
com/sun/jndi \
lib/testlibrary
diff --git a/test/jdk/jdk/incubator/concurrent/ScopedValue/ManyBindings.java b/test/jdk/java/lang/ScopedValue/ManyBindings.java
similarity index 95%
rename from test/jdk/jdk/incubator/concurrent/ScopedValue/ManyBindings.java
rename to test/jdk/java/lang/ScopedValue/ManyBindings.java
index 2bb35cc2c64..48751ae039f 100644
--- a/test/jdk/jdk/incubator/concurrent/ScopedValue/ManyBindings.java
+++ b/test/jdk/java/lang/ScopedValue/ManyBindings.java
@@ -23,15 +23,14 @@
/*
* @test
- * @summary Stress test ScopedValue with many bindings and rebinings
- * @modules jdk.incubator.concurrent
+ * @summary Stress test ScopedValue with many bindings and rebindings
+ * @enablePreview
* @library /test/lib
* @key randomness
* @run junit ManyBindings
*/
-import jdk.incubator.concurrent.ScopedValue;
-import jdk.incubator.concurrent.ScopedValue.Carrier;
+import java.lang.ScopedValue.Carrier;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;
@@ -129,7 +128,7 @@ class ManyBindings {
test(newArray, depth+1);
});
- // check that the scoped values have the origina values
+ // check that the scoped values have the original values
check(array);
}
diff --git a/test/jdk/jdk/incubator/concurrent/ScopedValue/ScopeValueAPI.java b/test/jdk/java/lang/ScopedValue/ScopedValueAPI.java
similarity index 71%
rename from test/jdk/jdk/incubator/concurrent/ScopedValue/ScopeValueAPI.java
rename to test/jdk/java/lang/ScopedValue/ScopedValueAPI.java
index c51cee3b1c1..90f151ae8e9 100644
--- a/test/jdk/jdk/incubator/concurrent/ScopedValue/ScopeValueAPI.java
+++ b/test/jdk/java/lang/ScopedValue/ScopedValueAPI.java
@@ -24,16 +24,16 @@
/*
* @test
* @summary Test ScopedValue API
- * @modules jdk.incubator.concurrent
- * @run junit ScopeValueAPI
+ * @enablePreview
+ * @run junit ScopedValueAPI
*/
-import jdk.incubator.concurrent.ScopedValue;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
+import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -41,7 +41,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
-class ScopeValueAPI {
+class ScopedValueAPI {
private static Stream factories() {
return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
@@ -56,7 +56,7 @@ class ScopeValueAPI {
test(factory, () -> {
class Box { static boolean executed; }
ScopedValue name = ScopedValue.newInstance();
- ScopedValue.where(name, "duke", () -> { Box.executed = true; });
+ ScopedValue.runWhere(name, "duke", () -> { Box.executed = true; });
assertTrue(Box.executed);
});
}
@@ -71,7 +71,7 @@ class ScopeValueAPI {
class FooException extends RuntimeException { }
ScopedValue name = ScopedValue.newInstance();
Runnable op = () -> { throw new FooException(); };
- assertThrows(FooException.class, () -> ScopedValue.where(name, "duke", op));
+ assertThrows(FooException.class, () -> ScopedValue.runWhere(name, "duke", op));
assertFalse(name.isBound());
});
}
@@ -84,7 +84,20 @@ class ScopeValueAPI {
void testCall(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue name = ScopedValue.newInstance();
- String result = ScopedValue.where(name, "duke", name::get);
+ String result = ScopedValue.callWhere(name, "duke", name::get);
+ assertEquals("duke", result);
+ });
+ }
+
+ /**
+ * Test that the get method is invoked.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testGetWhere(ThreadFactory factory) throws Exception {
+ test(factory, () -> {
+ ScopedValue name = ScopedValue.newInstance();
+ String result = ScopedValue.getWhere(name, "duke", (Supplier)(name::get));
assertEquals("duke", result);
});
}
@@ -99,7 +112,22 @@ class ScopeValueAPI {
class FooException extends RuntimeException { }
ScopedValue name = ScopedValue.newInstance();
Callable op = () -> { throw new FooException(); };
- assertThrows(FooException.class, () -> ScopedValue.where(name, "duke", op));
+ assertThrows(FooException.class, () -> ScopedValue.callWhere(name, "duke", op));
+ assertFalse(name.isBound());
+ });
+ }
+
+ /**
+ * Test the get(Supplier) method throwing an exception.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testGetThrows(ThreadFactory factory) throws Exception {
+ test(factory, () -> {
+ class FooException extends RuntimeException { }
+ ScopedValue name = ScopedValue.newInstance();
+ Supplier op = () -> { throw new FooException(); };
+ assertThrows(FooException.class, () -> ScopedValue.getWhere(name, "duke", op));
assertFalse(name.isBound());
});
}
@@ -117,7 +145,7 @@ class ScopeValueAPI {
assertThrows(NoSuchElementException.class, name2::get);
// run
- ScopedValue.where(name1, "duke", () -> {
+ ScopedValue.runWhere(name1, "duke", () -> {
assertEquals("duke", name1.get());
assertThrows(NoSuchElementException.class, name2::get);
@@ -126,7 +154,16 @@ class ScopeValueAPI {
assertThrows(NoSuchElementException.class, name2::get);
// call
- ScopedValue.where(name1, "duke", () -> {
+ ScopedValue.callWhere(name1, "duke", () -> {
+ assertEquals("duke", name1.get());
+ assertThrows(NoSuchElementException.class, name2::get);
+ return null;
+ });
+ assertThrows(NoSuchElementException.class, name1::get);
+ assertThrows(NoSuchElementException.class, name2::get);
+
+ // get
+ ScopedValue.getWhere(name1, "duke", () -> {
assertEquals("duke", name1.get());
assertThrows(NoSuchElementException.class, name2::get);
return null;
@@ -149,7 +186,7 @@ class ScopeValueAPI {
assertFalse(name2.isBound());
// run
- ScopedValue.where(name1, "duke", () -> {
+ ScopedValue.runWhere(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
});
@@ -157,7 +194,16 @@ class ScopeValueAPI {
assertFalse(name2.isBound());
// call
- ScopedValue.where(name1, "duke", () -> {
+ ScopedValue.callWhere(name1, "duke", () -> {
+ assertTrue(name1.isBound());
+ assertFalse(name2.isBound());
+ return null;
+ });
+ assertFalse(name1.isBound());
+ assertFalse(name2.isBound());
+
+ // call
+ ScopedValue.callWhere(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
return null;
@@ -179,13 +225,13 @@ class ScopeValueAPI {
assertEquals("default", name.orElse("default"));
// run
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.runWhere(name, "duke", () -> {
assertEquals("duke", name.orElse(null));
assertEquals("duke", name.orElse("default"));
});
// call
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.callWhere(name, "duke", () -> {
assertEquals("duke", name.orElse(null));
assertEquals("duke", name.orElse("default"));
return null;
@@ -205,12 +251,12 @@ class ScopeValueAPI {
assertThrows(FooException.class, () -> name.orElseThrow(FooException::new));
// run
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.runWhere(name, "duke", () -> {
assertEquals("duke", name.orElseThrow(FooException::new));
});
// call
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.callWhere(name, "duke", () -> {
assertEquals("duke", name.orElseThrow(FooException::new));
return null;
});
@@ -248,6 +294,17 @@ class ScopeValueAPI {
assertFalse(name.isBound());
assertFalse(age.isBound());
+ // get
+ ScopedValue.where(name, "duke").where(age, 100).get(() -> {
+ assertTrue(name.isBound());
+ assertTrue(age.isBound());
+ assertEquals("duke", name.get());
+ assertEquals(100, (int) age.get());
+ return null;
+ });
+ assertFalse(name.isBound());
+ assertFalse(age.isBound());
+
});
}
@@ -261,11 +318,11 @@ class ScopeValueAPI {
ScopedValue name = ScopedValue.newInstance();
// run
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.runWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
- ScopedValue.where(name, "duchess", () -> {
+ ScopedValue.runWhere(name, "duchess", () -> {
assertTrue(name.isBound());
assertEquals("duchess", name.get());
});
@@ -276,11 +333,28 @@ class ScopeValueAPI {
assertFalse(name.isBound());
// call
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.callWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
- ScopedValue.where(name, "duchess", () -> {
+ ScopedValue.callWhere(name, "duchess", () -> {
+ assertTrue(name.isBound());
+ assertEquals("duchess", name.get());
+ return null;
+ });
+
+ assertTrue(name.isBound());
+ assertEquals("duke", name.get());
+ return null;
+ });
+ assertFalse(name.isBound());
+
+ // get
+ ScopedValue.getWhere(name, "duke", () -> {
+ assertTrue(name.isBound());
+ assertEquals("duke", name.get());
+
+ ScopedValue.where(name, "duchess").get(() -> {
assertTrue(name.isBound());
assertEquals("duchess", name.get());
return null;
@@ -304,11 +378,11 @@ class ScopeValueAPI {
ScopedValue name = ScopedValue.newInstance();
// run
- ScopedValue.where(name, null, () -> {
+ ScopedValue.runWhere(name, null, () -> {
assertTrue(name.isBound());
assertNull(name.get());
- ScopedValue.where(name, "duchess", () -> {
+ ScopedValue.runWhere(name, "duchess", () -> {
assertTrue(name.isBound());
assertTrue("duchess".equals(name.get()));
});
@@ -319,11 +393,28 @@ class ScopeValueAPI {
assertFalse(name.isBound());
// call
- ScopedValue.where(name, null, () -> {
+ ScopedValue.callWhere(name, null, () -> {
assertTrue(name.isBound());
assertNull(name.get());
- ScopedValue.where(name, "duchess", () -> {
+ ScopedValue.callWhere(name, "duchess", () -> {
+ assertTrue(name.isBound());
+ assertTrue("duchess".equals(name.get()));
+ return null;
+ });
+
+ assertTrue(name.isBound());
+ assertNull(name.get());
+ return null;
+ });
+ assertFalse(name.isBound());
+
+ // getWhere
+ ScopedValue.getWhere(name, null, () -> {
+ assertTrue(name.isBound());
+ assertNull(name.get());
+
+ ScopedValue.getWhere(name, "duchess", () -> {
assertTrue(name.isBound());
assertTrue("duchess".equals(name.get()));
return null;
@@ -347,11 +438,11 @@ class ScopeValueAPI {
ScopedValue name = ScopedValue.newInstance();
// run
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.runWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
- ScopedValue.where(name, null, () -> {
+ ScopedValue.runWhere(name, null, () -> {
assertTrue(name.isBound());
assertNull(name.get());
});
@@ -362,11 +453,28 @@ class ScopeValueAPI {
assertFalse(name.isBound());
// call
- ScopedValue.where(name, "duke", () -> {
+ ScopedValue.callWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
- ScopedValue.where(name, null, () -> {
+ ScopedValue.callWhere(name, null, () -> {
+ assertTrue(name.isBound());
+ assertNull(name.get());
+ return null;
+ });
+
+ assertTrue(name.isBound());
+ assertEquals("duke", name.get());
+ return null;
+ });
+ assertFalse(name.isBound());
+
+ // get
+ ScopedValue.where(name, "duke").get(() -> {
+ assertTrue(name.isBound());
+ assertEquals("duke", name.get());
+
+ ScopedValue.where(name, null).get(() -> {
assertTrue(name.isBound());
assertNull(name.get());
return null;
@@ -410,14 +518,15 @@ class ScopeValueAPI {
ScopedValue name = ScopedValue.newInstance();
assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value"));
- assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> { }));
- assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> null));
+ assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "value", () -> { }));
+ assertThrows(NullPointerException.class, () -> ScopedValue.getWhere(null, "value", () -> null));
assertThrows(NullPointerException.class, () -> name.orElseThrow(null));
var carrier = ScopedValue.where(name, "duke");
assertThrows(NullPointerException.class, () -> carrier.where(null, "value"));
- assertThrows(NullPointerException.class, () -> carrier.get(null));
+ assertThrows(NullPointerException.class, () -> carrier.get((ScopedValue>)null));
+ assertThrows(NullPointerException.class, () -> carrier.get((Supplier>)null));
assertThrows(NullPointerException.class, () -> carrier.run(null));
assertThrows(NullPointerException.class, () -> carrier.call(null));
}
diff --git a/test/jdk/jdk/incubator/concurrent/ScopedValue/StressStackOverflow.java b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java
similarity index 90%
rename from test/jdk/jdk/incubator/concurrent/ScopedValue/StressStackOverflow.java
rename to test/jdk/java/lang/ScopedValue/StressStackOverflow.java
index 18d16b69240..aabd530f70e 100644
--- a/test/jdk/jdk/incubator/concurrent/ScopedValue/StressStackOverflow.java
+++ b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java
@@ -24,8 +24,7 @@
/**
* @test
* @summary StressStackOverflow the recovery path for ScopedValue
- * @modules jdk.incubator.concurrent
- * @compile StressStackOverflow.java
+ * @enablePreview
* @run main/othervm/timeout=300 -XX:-TieredCompilation StressStackOverflow
* @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 StressStackOverflow
* @run main/othervm/timeout=300 StressStackOverflow
@@ -34,9 +33,9 @@
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
-import jdk.incubator.concurrent.ScopedValue;
-import jdk.incubator.concurrent.StructureViolationException;
-import jdk.incubator.concurrent.StructuredTaskScope;
+import java.util.concurrent.StructureViolationException;
+import java.util.concurrent.StructuredTaskScope;
+import java.util.function.Supplier;
public class StressStackOverflow {
public static final ScopedValue el = ScopedValue.newInstance();
@@ -53,9 +52,16 @@ public class StressStackOverflow {
// Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable
// and Runnable interfaces. Which one gets tested depends on the constructor argument.
- class DeepRecursion implements Callable, Runnable {
+ class DeepRecursion implements Callable, Supplier, Runnable {
+
+ static enum Behaviour {
+ CALL, GET, RUN;
+ private static Behaviour[] values = values();
+ public static Behaviour choose(ThreadLocalRandom tlr) {
+ return values[tlr.nextInt(3)];
+ }
+ }
- static enum Behaviour {CALL, RUN}
final Behaviour behaviour;
public DeepRecursion(Behaviour behaviour) {
@@ -70,6 +76,8 @@ public class StressStackOverflow {
switch (behaviour) {
case CALL ->
ScopedValue.where(el, el.get() + 1).call(() -> fibonacci_pad(20, this));
+ case GET ->
+ ScopedValue.where(el, el.get() + 1).get(() -> fibonacci_pad(20, this));
case RUN ->
ScopedValue.where(el, el.get() + 1).run(() -> fibonacci_pad(20, this));
}
@@ -96,10 +104,14 @@ public class StressStackOverflow {
Thread.yield();
}
- public Object call() {
+ public Object get() {
run();
return null;
}
+
+ public Object call() {
+ return get();
+ }
}
static final Runnable nop = new Runnable() {
@@ -142,12 +154,12 @@ public class StressStackOverflow {
var threadFactory
= (tlr.nextBoolean() ? Thread.ofPlatform() : Thread.ofVirtual()).factory();
try (var scope = new StructuredTaskScope("", threadFactory)) {
- var future = scope.fork(() -> {
+ var handle = scope.fork(() -> {
op.run();
return null;
});
- future.get();
scope.join();
+ handle.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -159,9 +171,9 @@ public class StressStackOverflow {
try (var scope = new StructuredTaskScope()) {
try {
if (tlr.nextBoolean()) {
- // Repeatedly test Scoped Values set by ScopedValue::call() and ScopedValue::run()
+ // Repeatedly test Scoped Values set by ScopedValue::call(), get(), and run()
final var deepRecursion
- = new DeepRecursion(tlr.nextBoolean() ? DeepRecursion.Behaviour.CALL : DeepRecursion.Behaviour.RUN);
+ = new DeepRecursion(DeepRecursion.Behaviour.choose(tlr));
deepRecursion.run();
} else {
// Recursively run ourself until we get a stack overflow
diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java
new file mode 100644
index 00000000000..11e3f70b3ed
--- /dev/null
+++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java
@@ -0,0 +1,1712 @@
+/*
+ * Copyright (c) 2021, 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 id=platform
+ * @bug 8284199 8296779 8306647
+ * @summary Basic tests for StructuredTaskScope
+ * @enablePreview
+ * @run junit/othervm -DthreadFactory=platform StructuredTaskScopeTest
+ */
+
+/*
+ * @test id=virtual
+ * @enablePreview
+ * @run junit/othervm -DthreadFactory=virtual StructuredTaskScopeTest
+ */
+
+import java.time.Duration;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.StructuredTaskScope;
+import java.util.concurrent.StructuredTaskScope.Subtask;
+import java.util.concurrent.StructuredTaskScope.ShutdownOnSuccess;
+import java.util.concurrent.StructuredTaskScope.ShutdownOnFailure;
+import java.util.concurrent.StructureViolationException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import static java.lang.Thread.State.*;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import static org.junit.jupiter.api.Assertions.*;
+
+class StructuredTaskScopeTest {
+ private static ScheduledExecutorService scheduler;
+ private static List threadFactories;
+
+ @BeforeAll
+ static void setup() throws Exception {
+ scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ // thread factories
+ String value = System.getProperty("threadFactory");
+ List list = new ArrayList<>();
+ if (value == null || value.equals("platform"))
+ list.add(Thread.ofPlatform().factory());
+ if (value == null || value.equals("virtual"))
+ list.add(Thread.ofVirtual().factory());
+ assertTrue(list.size() > 0, "No thread factories for tests");
+ threadFactories = list;
+ }
+
+ @AfterAll
+ static void shutdown() {
+ scheduler.shutdown();
+ }
+
+ private static Stream factories() {
+ return threadFactories.stream();
+ }
+
+ /**
+ * Test that fork creates a new thread for each task.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkCreatesThread(ThreadFactory factory) throws Exception {
+ Set tids = ConcurrentHashMap.newKeySet();
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ for (int i = 0; i < 100; i++) {
+ scope.fork(() -> {
+ tids.add(Thread.currentThread().threadId());
+ return null;
+ });
+ }
+ scope.join();
+ }
+ assertEquals(100, tids.size());
+ }
+
+ /**
+ * Test that fork creates a new virtual thread for each task.
+ */
+ @Test
+ void testForkCreateVirtualThread() throws Exception {
+ Set threads = ConcurrentHashMap.newKeySet();
+ try (var scope = new StructuredTaskScope()) {
+ for (int i = 0; i < 100; i++) {
+ scope.fork(() -> {
+ threads.add(Thread.currentThread());
+ return null;
+ });
+ }
+ scope.join();
+ }
+ assertEquals(100, threads.size());
+ threads.forEach(t -> assertTrue(t.isVirtual()));
+ }
+
+ /**
+ * Test that fork creates a new thread with the given thread factory.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkUsesFactory(ThreadFactory factory) throws Exception {
+ var count = new AtomicInteger();
+ ThreadFactory countingFactory = task -> {
+ count.incrementAndGet();
+ return factory.newThread(task);
+ };
+ try (var scope = new StructuredTaskScope(null, countingFactory)) {
+ for (int i = 0; i < 100; i++) {
+ scope.fork(() -> null);
+ }
+ scope.join();
+ }
+ assertEquals(100, count.get());
+ }
+
+ /**
+ * Test fork is confined to threads in the scope "tree".
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkConfined(ThreadFactory factory) throws Exception {
+ try (var scope1 = new StructuredTaskScope();
+ var scope2 = new StructuredTaskScope()) {
+
+ // thread in scope1 cannot fork thread in scope2
+ Subtask subtask1 = scope1.fork(() -> {
+ assertThrows(WrongThreadException.class, () -> {
+ scope2.fork(() -> null);
+ });
+ return true;
+ });
+
+ // thread in scope2 can fork thread in scope1
+ Subtask subtask2 = scope2.fork(() -> {
+ scope1.fork(() -> null);
+ return true;
+ });
+
+ scope2.join();
+ scope1.join();
+
+ assertTrue(subtask1.get());
+ assertTrue(subtask2.get());
+
+ // random thread cannot fork
+ try (var pool = Executors.newSingleThreadExecutor()) {
+ Future future = pool.submit(() -> {
+ assertThrows(WrongThreadException.class, () -> {
+ scope1.fork(() -> null);
+ });
+ assertThrows(WrongThreadException.class, () -> {
+ scope2.fork(() -> null);
+ });
+ return null;
+ });
+ future.get();
+ }
+ }
+ }
+
+ /**
+ * Test fork after join completes.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkAfterJoin(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ // round 1
+ var subtask1 = scope.fork(() -> "foo");
+ assertThrows(IllegalStateException.class, subtask1::get);
+ scope.join();
+ assertEquals("foo", subtask1.get());
+
+ // round 2
+ var subtask2 = scope.fork(() -> "bar");
+ assertEquals("foo", subtask1.get());
+ assertThrows(IllegalStateException.class, subtask2::get);
+ scope.join();
+ assertEquals("foo", subtask1.get());
+ assertEquals("bar", subtask2.get());
+
+ // round 3
+ var subtask3 = scope.fork(() -> "baz");
+ assertEquals("foo", subtask1.get());
+ assertEquals("bar", subtask2.get());
+ assertThrows(IllegalStateException.class, subtask3::get);
+ scope.join();
+ assertEquals("foo", subtask1.get());
+ assertEquals("bar", subtask2.get());
+ assertEquals("baz", subtask3.get());
+ }
+ }
+
+ /**
+ * Test fork after join throws.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkAfterJoinThrows(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var latch = new CountDownLatch(1);
+ var subtask1 = scope.fork(() -> {
+ latch.await();
+ return "foo";
+ });
+
+ // join throws
+ Thread.currentThread().interrupt();
+ assertThrows(InterruptedException.class, scope::join);
+
+ // allow subtask1 to finish
+ latch.countDown();
+
+ // continue to fork
+ var subtask2 = scope.fork(() -> "bar");
+ assertThrows(IllegalStateException.class, subtask1::get);
+ assertThrows(IllegalStateException.class, subtask2::get);
+ scope.join();
+ assertEquals("foo", subtask1.get());
+ assertEquals("bar", subtask2.get());
+ }
+ }
+
+ /**
+ * Test fork after scope is shutdown.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkAfterShutdown(ThreadFactory factory) throws Exception {
+ var executed = new AtomicBoolean();
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ scope.shutdown();
+ Subtask subtask = scope.fork(() -> {
+ executed.set(true);
+ return null;
+ });
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ assertThrows(IllegalStateException.class, subtask::get);
+ assertThrows(IllegalStateException.class, subtask::exception);
+ }
+ assertFalse(executed.get());
+ }
+
+ /**
+ * Test fork after scope is closed.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testForkAfterClose(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ scope.close();
+ assertThrows(IllegalStateException.class, () -> scope.fork(() -> null));
+ }
+ }
+
+ /**
+ * Test fork when the thread factory rejects creating a thread.
+ */
+ @Test
+ void testForkRejectedExecutionException() throws Exception {
+ ThreadFactory factory = task -> null;
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null));
+ scope.join();
+ }
+ }
+
+ /**
+ * Test join with no subtasks.
+ */
+ @Test
+ void testJoinWithNoSubtasks() throws Exception {
+ try (var scope = new StructuredTaskScope()) {
+ scope.join();
+ }
+ }
+
+ /**
+ * Test join with unfinished subtasks.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinWithSubtasks(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ Subtask subtask = scope.fork(() -> {
+ Thread.sleep(Duration.ofMillis(50));
+ return "foo";
+ });
+ scope.join();
+ assertEquals("foo", subtask.get());
+ }
+ }
+
+ /**
+ * Test join is owner confined.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinConfined(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope()) {
+
+ // thread in scope cannot join
+ Subtask subtask = scope.fork(() -> {
+ assertThrows(WrongThreadException.class, () -> { scope.join(); });
+ return true;
+ });
+
+ scope.join();
+
+ assertTrue(subtask.get());
+
+ // random thread cannot join
+ try (var pool = Executors.newSingleThreadExecutor()) {
+ Future future = pool.submit(() -> {
+ assertThrows(WrongThreadException.class, scope::join);
+ return null;
+ });
+ future.get();
+ }
+ }
+ }
+
+ /**
+ * Test join with interrupt status set.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testInterruptJoin1(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var latch = new CountDownLatch(1);
+
+ Subtask subtask = scope.fork(() -> {
+ latch.await();
+ return "foo";
+ });
+
+ // join should throw
+ Thread.currentThread().interrupt();
+ try {
+ scope.join();
+ fail("join did not throw");
+ } catch (InterruptedException expected) {
+ assertFalse(Thread.interrupted()); // interrupt status should be clear
+ } finally {
+ // let task continue
+ latch.countDown();
+ }
+
+ // join should complete
+ scope.join();
+ assertEquals("foo", subtask.get());
+ }
+ }
+
+ /**
+ * Test interrupt of thread blocked in join.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testInterruptJoin2(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var latch = new CountDownLatch(1);
+ Subtask subtask = scope.fork(() -> {
+ latch.await();
+ return "foo";
+ });
+
+ // join should throw
+ scheduleInterruptAt("java.util.concurrent.StructuredTaskScope.join");
+ try {
+ scope.join();
+ fail("join did not throw");
+ } catch (InterruptedException expected) {
+ assertFalse(Thread.interrupted()); // interrupt status should be clear
+ } finally {
+ // let task continue
+ latch.countDown();
+ }
+
+ // join should complete
+ scope.join();
+ assertEquals("foo", subtask.get());
+ }
+ }
+
+ /**
+ * Test join when scope is shutdown.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinWithShutdown1(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var interrupted = new CountDownLatch(1);
+ var finish = new CountDownLatch(1);
+
+ Subtask subtask = scope.fork(() -> {
+ try {
+ Thread.sleep(Duration.ofDays(1));
+ } catch (InterruptedException e) {
+ interrupted.countDown();
+ }
+ finish.await();
+ return "foo";
+ });
+
+ scope.shutdown(); // should interrupt task
+
+ interrupted.await();
+
+ scope.join();
+
+ // signal task to finish
+ finish.countDown();
+ }
+ }
+
+ /**
+ * Test shutdown when owner is blocked in join.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinWithShutdown2(ThreadFactory factory) throws Exception {
+ class MyScope extends StructuredTaskScope {
+ MyScope(ThreadFactory factory) {
+ super(null, factory);
+ }
+ @Override
+ protected void handleComplete(Subtask extends T> subtask) {
+ shutdown();
+ }
+ }
+
+ try (var scope = new MyScope(factory)) {
+ Subtask subtask1 = scope.fork(() -> {
+ Thread.sleep(Duration.ofMillis(50));
+ return "foo";
+ });
+ Subtask subtask2 = scope.fork(() -> {
+ Thread.sleep(Duration.ofDays(1));
+ return "bar";
+ });
+
+ // join should wakeup when shutdown is called
+ scope.join();
+
+ // task1 should have completed successfully
+ assertEquals(Subtask.State.SUCCESS, subtask1.state());
+ assertEquals("foo", subtask1.get());
+ assertThrows(IllegalStateException.class, subtask1::exception);
+
+ // task2 result/exception not available
+ assertEquals(Subtask.State.UNAVAILABLE, subtask2.state());
+ assertThrows(IllegalStateException.class, subtask2::get);
+ assertThrows(IllegalStateException.class, subtask2::exception);
+ }
+ }
+
+ /**
+ * Test join after scope is closed.
+ */
+ @Test
+ void testJoinAfterClose() throws Exception {
+ try (var scope = new StructuredTaskScope()) {
+ scope.join();
+ scope.close();
+ assertThrows(IllegalStateException.class, () -> scope.join());
+ assertThrows(IllegalStateException.class, () -> scope.joinUntil(Instant.now()));
+ }
+ }
+
+ /**
+ * Test joinUntil, subtasks finish before deadline expires.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinUntil1(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ Subtask subtask = scope.fork(() -> {
+ try {
+ Thread.sleep(Duration.ofSeconds(2));
+ } catch (InterruptedException e) { }
+ return "foo";
+ });
+
+ long startMillis = millisTime();
+ scope.joinUntil(Instant.now().plusSeconds(30));
+ expectDuration(startMillis, /*min*/1900, /*max*/20_000);
+ assertEquals("foo", subtask.get());
+ }
+ }
+
+ /**
+ * Test joinUntil, deadline expires before subtasks finish.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinUntil2(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ Subtask subtask = scope.fork(() -> {
+ Thread.sleep(Duration.ofDays(1));
+ return null;
+ });
+
+ long startMillis = millisTime();
+ try {
+ scope.joinUntil(Instant.now().plusSeconds(2));
+ } catch (TimeoutException e) {
+ expectDuration(startMillis, /*min*/1900, /*max*/20_000);
+ }
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ }
+ }
+
+ /**
+ * Test joinUntil many times.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinUntil3(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ Subtask subtask = scope.fork(() -> {
+ Thread.sleep(Duration.ofDays(1));
+ return null;
+ });
+
+ for (int i = 0; i < 3; i++) {
+ try {
+ scope.joinUntil(Instant.now().plusMillis(50));
+ fail("joinUntil did not throw");
+ } catch (TimeoutException expected) {
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ }
+ }
+ }
+ }
+
+ /**
+ * Test joinUntil with a deadline that has already expired.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testJoinUntil4(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ Subtask subtask = scope.fork(() -> {
+ Thread.sleep(Duration.ofDays(1));
+ return null;
+ });
+
+ // now
+ try {
+ scope.joinUntil(Instant.now());
+ fail("joinUntil did not throw");
+ } catch (TimeoutException expected) {
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ }
+
+ // in the past
+ try {
+ scope.joinUntil(Instant.now().minusSeconds(1));
+ fail("joinUntil did not throw");
+ } catch (TimeoutException expected) {
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ }
+ }
+ }
+
+ /**
+ * Test joinUntil with interrupt status set.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testInterruptJoinUntil1(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var latch = new CountDownLatch(1);
+
+ Subtask subtask = scope.fork(() -> {
+ latch.await();
+ return "foo";
+ });
+
+ // joinUntil should throw
+ Thread.currentThread().interrupt();
+ try {
+ scope.joinUntil(Instant.now().plusSeconds(30));
+ fail("joinUntil did not throw");
+ } catch (InterruptedException expected) {
+ assertFalse(Thread.interrupted()); // interrupt status should be clear
+ } finally {
+ // let task continue
+ latch.countDown();
+ }
+
+ // join should complete
+ scope.join();
+ assertEquals("foo", subtask.get());
+ }
+ }
+
+ /**
+ * Test interrupt of thread blocked in joinUntil.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testInterruptJoinUntil2(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var latch = new CountDownLatch(1);
+
+ Subtask subtask = scope.fork(() -> {
+ latch.await();
+ return "foo";
+ });
+
+ // joinUntil should throw
+ scheduleInterruptAt("java.util.concurrent.StructuredTaskScope.joinUntil");
+ try {
+ scope.joinUntil(Instant.now().plusSeconds(30));
+ fail("joinUntil did not throw");
+ } catch (InterruptedException expected) {
+ assertFalse(Thread.interrupted()); // interrupt status should be clear
+ } finally {
+ // let task continue
+ latch.countDown();
+ }
+
+ // join should complete
+ scope.join();
+ assertEquals("foo", subtask.get());
+ }
+ }
+
+ /**
+ * Test that shutdown prevents new threads from starting.
+ */
+ @Test
+ void testShutdownWithFork() throws Exception {
+ ThreadFactory factory = task -> null;
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ scope.shutdown();
+ // should not invoke the ThreadFactory to create thread
+ Subtask subtask = scope.fork(() -> null);
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ assertThrows(IllegalStateException.class, subtask::get);
+ assertThrows(IllegalStateException.class, subtask::exception);
+ }
+ }
+
+ /**
+ * Test that shutdown interrupts unfinished subtasks.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testShutdownInterruptsThreads1(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var interrupted = new AtomicBoolean();
+ var latch = new CountDownLatch(1);
+ var subtask = scope.fork(() -> {
+ try {
+ Thread.sleep(Duration.ofDays(1));
+ } catch (InterruptedException e) {
+ interrupted.set(true);
+ } finally {
+ latch.countDown();
+ }
+ return null;
+ });
+
+ scope.shutdown();
+
+ // wait for task to complete
+ latch.await();
+ assertTrue(interrupted.get());
+
+ scope.join();
+
+ // subtask result/exception not available
+ assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
+ assertThrows(IllegalStateException.class, subtask::get);
+ assertThrows(IllegalStateException.class, subtask::exception);
+ }
+ }
+
+ /**
+ * Test that shutdown does not interrupt current thread.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testShutdownInterruptsThreads2(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var interrupted = new AtomicBoolean();
+ var latch = new CountDownLatch(1);
+ var subtask = scope.fork(() -> {
+ try {
+ scope.shutdown();
+ interrupted.set(Thread.currentThread().isInterrupted());
+ } finally {
+ latch.countDown();
+ }
+ return null;
+ });
+
+ // wait for task to complete
+ latch.await();
+ assertFalse(interrupted.get());
+
+ scope.join();
+ }
+ }
+
+ /**
+ * Test shutdown wakes join.
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testShutdownWakesJoin(ThreadFactory factory) throws Exception {
+ try (var scope = new StructuredTaskScope(null, factory)) {
+ var latch = new CountDownLatch(1);
+ scope.fork(() -> {
+ Thread.sleep(Duration.ofMillis(100)); // give time for join to block
+ scope.shutdown();
+ latch.await();
+ return null;
+ });
+
+ scope.join();
+
+ // join woke up, allow task to complete
+ latch.countDown();
+ }
+ }
+
+ /**
+ * Test shutdown after scope is closed.
+ */
+ @Test
+ void testShutdownAfterClose() throws Exception {
+ try (var scope = new StructuredTaskScope()) {
+ scope.join();
+ scope.close();
+ assertThrows(IllegalStateException.class, scope::shutdown);
+ }
+ }
+
+ /**
+ * Test shutdown is confined to threads in the scope "tree".
+ */
+ @ParameterizedTest
+ @MethodSource("factories")
+ void testShutdownConfined(ThreadFactory factory) throws Exception {
+ try (var scope1 = new StructuredTaskScope();
+ var scope2 = new StructuredTaskScope()) {
+
+ // thread in scope1 cannot shutdown scope2
+ Subtask subtask1 = scope1.fork(() -> {
+ assertThrows(WrongThreadException.class, scope2::shutdown);
+ return true;
+ });
+
+ // wait for task in scope1 to complete to avoid racing with task in scope2
+ while (subtask1.state() == Subtask.State.UNAVAILABLE) {
+ Thread.sleep(10);
+ }
+
+ // thread in scope2 shutdown scope1
+ Subtask subtask2 = scope2.fork(() -> {
+ scope1.shutdown();
+ return true;
+ });
+
+ scope2.join();
+ scope1.join();
+
+ assertTrue(subtask1.get());
+ assertTrue(subtask1.get());
+
+ // random thread cannot shutdown
+ try (var pool = Executors.newSingleThreadExecutor()) {
+ Future