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). * *

Rebinding

* * 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}". * *

Inheritance

* - * {@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: + *

    + *
  1. {@code ThreadLocal} does not prevent code in a faraway callee from {@linkplain + * ThreadLocal#set(Object) setting} a new value. + *
  2. A {@code ThreadLocal} has an unbounded lifetime and thus continues to have a value + * after a method completes, unless explicitly {@linkplain ThreadLocal#remove() removed}. + *
  3. {@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 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 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 s; + CallableAdapter(Supplier 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 op) throws Exception { + public static R callWhere(ScopedValue key, + T value, + Callable 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 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: + *
    + *
  1. {@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. + *
  2. {@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 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. + * + *

Tree structure

+ * + * 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 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 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 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 scope; + private final Callable task; + private final int round; + private volatile Object result; + + SubtaskImpl(StructuredTaskScope scope, + Callable 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 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 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: - *
    - *
  1. {@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. - *
  2. {@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(); - * } - * } - * } - * - *

Tree structure

- * - * 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 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 scope, Callable 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 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 future = pool.submit(() -> { + assertThrows(WrongThreadException.class, scope1::shutdown); + assertThrows(WrongThreadException.class, scope2::shutdown); + return null; + }); + future.get(); + } + } + } + + /** + * Test isShutdown. + */ + @Test + void testIsShutdown() { + try (var scope = new StructuredTaskScope()) { + assertFalse(scope.isShutdown()); // before shutdown + scope.shutdown(); + assertTrue(scope.isShutdown()); // after shutdown + scope.close(); + assertTrue(scope.isShutdown()); // after cose + } + } + + /** + * Test close without join, no subtasks forked. + */ + @Test + void testCloseWithoutJoin1() { + try (var scope = new StructuredTaskScope()) { + // do nothing + } + } + + /** + * Test close without join, unfinished subtasks. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseWithoutJoin2(ThreadFactory factory) { + try (var scope = new StructuredTaskScope(null, factory)) { + Subtask subtask = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + assertThrows(IllegalStateException.class, scope::close); + + // subtask result/exception not available + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + } + } + + /** + * Test close without join, unfinished subtasks forked after join. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseWithoutJoin3(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + scope.fork(() -> "foo"); + scope.join(); + + Subtask subtask = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + assertThrows(IllegalStateException.class, scope::close); + + // subtask result/exception not available + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + } + } + + /** + * Test close after join throws. Close should not throw as join attempted. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseAfterJoinThrows(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope()) { + var subtask = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + + // join throws + Thread.currentThread().interrupt(); + assertThrows(InterruptedException.class, scope::join); + assertThrows(IllegalStateException.class, subtask::get); + } + } + + /** + * Test close after joinUntil throws. Close should not throw as join attempted. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseAfterJoinUntilThrows(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope()) { + var subtask = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }); + + // joinUntil throws + assertThrows(TimeoutException.class, () -> scope.joinUntil(Instant.now())); + assertThrows(IllegalStateException.class, subtask::get); + } + } + + /** + * Test close is owner confined. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseConfined(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope()) { + + // attempt to close from thread in scope + Subtask subtask = scope.fork(() -> { + assertThrows(WrongThreadException.class, scope::close); + return true; + }); + + scope.join(); + assertTrue(subtask.get()); + + // random thread cannot close scope + try (var pool = Executors.newCachedThreadPool(factory)) { + Future future = pool.submit(() -> { + assertThrows(WrongThreadException.class, scope::close); + return null; + }); + future.get(); + } + } + } + + /** + * Test close with interrupt status set. + */ + @ParameterizedTest + @MethodSource("factories") + void testInterruptClose1(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + var done = new AtomicBoolean(); + scope.fork(() -> { + try { + Thread.sleep(Duration.ofDays(1)); + } catch (InterruptedException e) { + // interrupted by shutdown, expected + } + Thread.sleep(Duration.ofMillis(100)); // force close to wait + done.set(true); + return null; + }); + + scope.shutdown(); + scope.join(); + + // invoke close with interrupt status set + Thread.currentThread().interrupt(); + try { + scope.close(); + } finally { + assertTrue(Thread.interrupted()); // clear interrupt status + assertTrue(done.get()); + } + } + } + + /** + * Test interrupting thread waiting in close. + */ + @ParameterizedTest + @MethodSource("factories") + void testInterruptClose2(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + var done = new AtomicBoolean(); + Thread mainThread = Thread.currentThread(); + scope.fork(() -> { + try { + Thread.sleep(Duration.ofDays(1)); + } catch (InterruptedException e) { + // interrupted by shutdown, expected + } + + // interrupt main thread when it blocks in close + interruptThreadAt(mainThread, "java.util.concurrent.StructuredTaskScope.close"); + + Thread.sleep(Duration.ofMillis(100)); // force close to wait + done.set(true); + return null; + }); + + scope.shutdown(); // interrupts task + scope.join(); + try { + scope.close(); + } finally { + assertTrue(Thread.interrupted()); // clear interrupt status + assertTrue(done.get()); + } + } + } + + /** + * Test that closing an enclosing scope closes the thread flock of a nested scope. + */ + @Test + void testCloseThrowsStructureViolation() throws Exception { + try (var scope1 = new StructuredTaskScope()) { + try (var scope2 = new StructuredTaskScope()) { + + // join + close enclosing scope + scope1.join(); + try { + scope1.close(); + fail("close did not throw"); + } catch (StructureViolationException expected) { } + + // underlying flock should be closed, fork should return a cancelled task + var executed = new AtomicBoolean(); + Subtask subtask = scope2.fork(() -> { + executed.set(true); + return null; + }); + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + scope2.join(); + assertFalse(executed.get()); + } + } + } + + /** + * A StructuredTaskScope that collects the subtasks notified to the handleComplete method. + */ + private static class CollectAll extends StructuredTaskScope { + private final Set> subtasks = ConcurrentHashMap.newKeySet(); + + CollectAll(ThreadFactory factory) { + super(null, factory); + } + + @Override + protected void handleComplete(Subtask subtask) { + subtasks.add(subtask); + } + + Set> subtasks() { + return subtasks; + } + + Subtask find(Callable task) { + return subtasks.stream() + .filter(h -> task.equals(h.task())) + .findAny() + .orElseThrow(); + } + } + + /** + * Test that handleComplete method is invoked for tasks that complete before shutdown. + */ + @ParameterizedTest + @MethodSource("factories") + void testHandleCompleteBeforeShutdown(ThreadFactory factory) throws Exception { + try (var scope = new CollectAll(factory)) { + Callable task1 = () -> "foo"; + Callable task2 = () -> { throw new FooException(); }; + scope.fork(task1); + scope.fork(task2); + scope.join(); + + var subtask1 = scope.find(task1); + assertEquals("foo", subtask1.get()); + + var subtask2 = scope.find(task2); + assertTrue(subtask2.exception() instanceof FooException); + } + } + + /** + * Test that handleComplete method is not invoked for tasks that finish after shutdown + * or are forked after shutdown. + */ + @ParameterizedTest + @MethodSource("factories") + void testHandleCompleteAfterShutdown(ThreadFactory factory) throws Exception { + try (var scope = new CollectAll(factory)) { + Callable task1 = () -> { + try { + Thread.sleep(Duration.ofDays(1)); + } catch (InterruptedException ignore) { } + return "foo"; + }; + Callable task2 = () -> { + Thread.sleep(Duration.ofDays(1)); + return "bar"; + }; + Callable task3 = () -> "baz"; + + // forked before shutdown, will complete after shutdown + var subtask1 = scope.fork(task1); + var subtask2 = scope.fork(task2); + + scope.shutdown(); + + // forked after shutdown + var subtask3 = scope.fork(task3); + + scope.join(); + + // handleComplete should not be called + for (int i = 0; i < 3; i++) { + assertEquals(0, scope.subtasks().size()); + Thread.sleep(20); + } + + assertEquals(Subtask.State.UNAVAILABLE, subtask1.state()); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + assertEquals(Subtask.State.UNAVAILABLE, subtask3.state()); + } + } + + /** + * Test that the default handleComplete throws IllegalArgumentException if called + * with a running task. + */ + @Test + void testHandleCompleteThrows() throws Exception { + class TestScope extends StructuredTaskScope { + protected void handleComplete(Subtask subtask) { + super.handleComplete(subtask); + } + } + + try (var scope = new TestScope()) { + var subtask = scope.fork(() -> { + Thread.sleep(Duration.ofDays(1)); + return "foo"; + }); + + // running task + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalArgumentException.class, () -> scope.handleComplete(subtask)); + scope.shutdown(); + + // null task + assertThrows(NullPointerException.class, () -> scope.handleComplete(null)); + + scope.join(); + } + } + + /** + * Test ensureOwnerAndJoined. + */ + @ParameterizedTest + @MethodSource("factories") + void testEnsureOwnerAndJoined(ThreadFactory factory) throws Exception { + class MyScope extends StructuredTaskScope { + MyScope(ThreadFactory factory) { + super(null, factory); + } + void invokeEnsureOwnerAndJoined() { + super.ensureOwnerAndJoined(); + } + } + + try (var scope = new MyScope(factory)) { + // owner thread, before join + scope.fork(() -> true); + assertThrows(IllegalStateException.class, () -> { + scope.invokeEnsureOwnerAndJoined(); + }); + + // owner thread, after join + scope.join(); + scope.invokeEnsureOwnerAndJoined(); + + // thread in scope cannot invoke ensureOwnerAndJoined + Subtask subtask = scope.fork(() -> { + assertThrows(WrongThreadException.class, () -> { + scope.invokeEnsureOwnerAndJoined(); + }); + return true; + }); + scope.join(); + assertTrue(subtask.get()); + + // random thread cannot invoke ensureOwnerAndJoined + try (var pool = Executors.newSingleThreadExecutor()) { + Future future = pool.submit(() -> { + assertThrows(WrongThreadException.class, () -> { + scope.invokeEnsureOwnerAndJoined(); + }); + return null; + }); + future.get(); + } + } + } + + /** + * Test ensureOwnerAndJoined after the task scope has been closed. + */ + @ParameterizedTest + @MethodSource("factories") + void testEnsureOwnerAndJoinedAfterClose(ThreadFactory factory) throws Exception { + class MyScope extends StructuredTaskScope { + MyScope(ThreadFactory factory) { + super(null, factory); + } + public void invokeEnsureOwnerAndJoined() { + super.ensureOwnerAndJoined(); + } + } + + // ensureOwnerAndJoined after close, join invoked + try (var scope = new MyScope(factory)) { + scope.fork(() -> "foo"); + scope.join(); + scope.close(); + scope.invokeEnsureOwnerAndJoined(); // should not throw + } + + // ensureOwnerAndJoined after close, join not invoked + try (var scope = new MyScope(factory)) { + scope.fork(() -> "foo"); + assertThrows(IllegalStateException.class, scope::close); + scope.invokeEnsureOwnerAndJoined(); // should not throw + } + } + + + /** + * Test toString. + */ + @Test + void testToString() throws Exception { + ThreadFactory factory = Thread.ofVirtual().factory(); + try (var scope = new StructuredTaskScope("duke", factory)) { + // open + assertTrue(scope.toString().contains("duke")); + + // shutdown + scope.shutdown(); + assertTrue(scope.toString().contains("duke")); + + // closed + scope.join(); + scope.close(); + assertTrue(scope.toString().contains("duke")); + } + } + + /** + * Test Subtask with task that completes successfully. + */ + @ParameterizedTest + @MethodSource("factories") + void testSubtaskWhenSuccess(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Callable task = () -> "foo"; + Subtask subtask = scope.fork(task); + + // before join, owner thread + assertEquals(task, subtask.task()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + + scope.join(); + + // after join + assertEquals(task, subtask.task()); + assertEquals(Subtask.State.SUCCESS, subtask.state()); + assertEquals("foo", subtask.get()); + assertThrows(IllegalStateException.class, subtask::exception); + } + } + + /** + * Test Subtask with task that fails. + */ + @ParameterizedTest + @MethodSource("factories") + void testSubtaskWhenFailed(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Callable task = () -> { throw new FooException(); }; + Subtask subtask = scope.fork(task); + + // before join, owner thread + assertEquals(task, subtask.task()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + + scope.join(); + + // after join + assertEquals(task, subtask.task()); + assertEquals(Subtask.State.FAILED, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertTrue(subtask.exception() instanceof FooException); + } + } + + /** + * Test Subtask with a task that has not completed. + */ + @ParameterizedTest + @MethodSource("factories") + void testSubtaskWhenNotCompleted(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Callable task = () -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }; + Subtask subtask = scope.fork(task); + + // before join + assertEquals(task, subtask.task()); + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + + // attempt join, join throws + Thread.currentThread().interrupt(); + assertThrows(InterruptedException.class, scope::join); + + // after join + assertEquals(task, subtask.task()); + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + } + } + + /** + * Test Subtask when forked after shutdown. + */ + @ParameterizedTest + @MethodSource("factories") + void testSubtaskWhenShutdown(ThreadFactory factory) throws Exception { + try (var scope = new StructuredTaskScope(null, factory)) { + Callable task = () -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }; + + scope.shutdown(); + + // fork after shutdown + Subtask subtask = scope.fork(task); + assertEquals(task, subtask.task()); + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + } + } + + /** + * Test Subtask::toString. + */ + @Test + void testSubtaskToString() throws Exception { + try (var scope = new StructuredTaskScope()) { + // success + var subtask1 = scope.fork(() -> "foo"); + scope.join(); + assertTrue(subtask1.toString().contains("Completed successfully")); + + // failed + var subtask2 = scope.fork(() -> { throw new FooException(); }); + scope.join(); + assertTrue(subtask2.toString().contains("Failed")); + + // not completed + Callable sleepForDay = () -> { + Thread.sleep(Duration.ofDays(1)); + return null; + }; + var subtask3 = scope.fork(sleepForDay); + assertTrue(subtask3.toString().contains("Unavailable")); + + scope.shutdown(); + + // forked after shutdown + var subtask4 = scope.fork(sleepForDay); + assertTrue(subtask4.toString().contains("Unavailable")); + + scope.join(); + } + } + + /** + * Test ShutdownOnSuccess with no completed tasks. + */ + @Test + void testShutdownOnSuccess1() throws Exception { + try (var scope = new ShutdownOnSuccess()) { + assertThrows(IllegalStateException.class, () -> scope.result()); + assertThrows(IllegalStateException.class, () -> scope.result(e -> null)); + } + } + + /** + * Test ShutdownOnSuccess with tasks that complete successfully. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnSuccess2(ThreadFactory factory) throws Exception { + try (var scope = new ShutdownOnSuccess(null, factory)) { + scope.fork(() -> "foo"); + scope.join(); // ensures foo completes first + scope.fork(() -> "bar"); + scope.join(); + assertEquals("foo", scope.result()); + assertEquals("foo", scope.result(e -> null)); + } + } + + /** + * Test ShutdownOnSuccess with a task that completes successfully with a null result. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnSuccess3(ThreadFactory factory) throws Exception { + try (var scope = new ShutdownOnSuccess(null, factory)) { + scope.fork(() -> null); + scope.join(); + assertNull(scope.result()); + assertNull(scope.result(e -> null)); + } + } + + /** + * Test ShutdownOnSuccess with tasks that complete succcessfully and tasks that fail. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnSuccess4(ThreadFactory factory) throws Exception { + try (var scope = new ShutdownOnSuccess(null, factory)) { + scope.fork(() -> "foo"); + scope.fork(() -> { throw new ArithmeticException(); }); + scope.join(); + assertEquals("foo", scope.result()); + assertEquals("foo", scope.result(e -> null)); + } + } + + /** + * Test ShutdownOnSuccess with a task that fails. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnSuccess5(ThreadFactory factory) throws Exception { + try (var scope = new ShutdownOnSuccess(null, factory)) { + scope.fork(() -> { throw new ArithmeticException(); }); + scope.join(); + Throwable ex = assertThrows(ExecutionException.class, () -> scope.result()); + assertTrue(ex.getCause() instanceof ArithmeticException); + ex = assertThrows(FooException.class, () -> scope.result(e -> new FooException(e))); + assertTrue(ex.getCause() instanceof ArithmeticException); + } + } + + /** + * Test ShutdownOnSuccess methods are confined to the owner. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnSuccessConfined(ThreadFactory factory) throws Exception { + // owner before join + try (var scope = new ShutdownOnSuccess(null, factory)) { + scope.fork(() -> { throw new FooException(); }); + assertThrows(IllegalStateException.class, scope::result); + assertThrows(IllegalStateException.class, () -> { + scope.result(e -> new RuntimeException(e)); + }); + scope.join(); + } + + // non-owner + try (var scope = new ShutdownOnSuccess(null, factory)) { + Subtask subtask = scope.fork(() -> { + assertThrows(WrongThreadException.class, scope::result); + assertThrows(WrongThreadException.class, () -> { + scope.result(e -> new RuntimeException(e)); + }); + return true; + }); + scope.join(); + assertTrue(subtask.get()); + } + } + + /** + * Test ShutdownOnFailure with no completed tasks. + */ + @Test + void testShutdownOnFailure1() throws Throwable { + try (var scope = new ShutdownOnFailure()) { + assertTrue(scope.exception().isEmpty()); + scope.throwIfFailed(); + scope.throwIfFailed(e -> new FooException(e)); + } + } + + /** + * Test ShutdownOnFailure with tasks that complete successfully. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnFailure2(ThreadFactory factory) throws Throwable { + try (var scope = new ShutdownOnFailure(null, factory)) { + scope.fork(() -> "foo"); + scope.fork(() -> "bar"); + scope.join(); + + // no exception + assertTrue(scope.exception().isEmpty()); + scope.throwIfFailed(); + scope.throwIfFailed(e -> new FooException(e)); + } + } + + /** + * Test ShutdownOnFailure with tasks that complete succcessfully and tasks that fail. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnFailure3(ThreadFactory factory) throws Throwable { + try (var scope = new ShutdownOnFailure(null, factory)) { + + // one task completes successfully, the other fails + scope.fork(() -> "foo"); + scope.fork(() -> { throw new ArithmeticException(); }); + scope.join(); + + Throwable ex = scope.exception().orElse(null); + assertTrue(ex instanceof ArithmeticException); + + ex = assertThrows(ExecutionException.class, () -> scope.throwIfFailed()); + assertTrue(ex.getCause() instanceof ArithmeticException); + + ex = assertThrows(FooException.class, + () -> scope.throwIfFailed(e -> new FooException(e))); + assertTrue(ex.getCause() instanceof ArithmeticException); + } + } + + /** + * Test ShutdownOnFailure methods are confined to the owner. + */ + @ParameterizedTest + @MethodSource("factories") + void testShutdownOnFailureConfined(ThreadFactory factory) throws Exception { + // owner before join + try (var scope = new ShutdownOnFailure(null, factory)) { + scope.fork(() -> "foo"); + assertThrows(IllegalStateException.class, scope::exception); + assertThrows(IllegalStateException.class, scope::throwIfFailed); + assertThrows(IllegalStateException.class, () -> { + scope.throwIfFailed(e -> new RuntimeException(e)); + }); + scope.join(); + } + + // non-owner + try (var scope = new ShutdownOnFailure(null, factory)) { + Subtask subtask = scope.fork(() -> { + assertThrows(WrongThreadException.class, scope::exception); + assertThrows(WrongThreadException.class, scope::throwIfFailed); + assertThrows(WrongThreadException.class, () -> { + scope.throwIfFailed(e -> new RuntimeException(e)); + }); + return true; + }); + scope.join(); + assertTrue(subtask.get()); + } + } + + /** + * Test for NullPointerException. + */ + @Test + void testNulls() throws Exception { + assertThrows(NullPointerException.class, () -> new StructuredTaskScope("", null)); + try (var scope = new StructuredTaskScope()) { + assertThrows(NullPointerException.class, () -> scope.fork(null)); + assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); + } + + assertThrows(NullPointerException.class, () -> new ShutdownOnSuccess("", null)); + try (var scope = new ShutdownOnSuccess()) { + assertThrows(NullPointerException.class, () -> scope.fork(null)); + assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); + assertThrows(NullPointerException.class, () -> scope.result(null)); + } + + assertThrows(NullPointerException.class, () -> new ShutdownOnFailure("", null)); + try (var scope = new ShutdownOnFailure()) { + assertThrows(NullPointerException.class, () -> scope.fork(null)); + assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); + assertThrows(NullPointerException.class, () -> scope.throwIfFailed(null)); + } + } + + /** + * A runtime exception for tests. + */ + private static class FooException extends RuntimeException { + FooException() { } + FooException(Throwable cause) { super(cause); } + } + + /** + * Returns the current time in milliseconds. + */ + private long millisTime() { + long now = System.nanoTime(); + return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); + } + + /** + * Check the duration of a task + * @param start start time, in milliseconds + * @param min minimum expected duration, in milliseconds + * @param max maximum expected duration, in milliseconds + * @return the duration (now - start), in milliseconds + */ + private long expectDuration(long start, long min, long max) { + long duration = millisTime() - start; + assertTrue(duration >= min, + "Duration " + duration + "ms, expected >= " + min + "ms"); + assertTrue(duration <= max, + "Duration " + duration + "ms, expected <= " + max + "ms"); + return duration; + } + + /** + * Interrupts a thread when it waits (timed or untimed) at location "{@code c.m}". + * {@code c} is the fully qualified class name and {@code m} is the method name. + */ + private void interruptThreadAt(Thread target, String location) throws InterruptedException { + int index = location.lastIndexOf('.'); + String className = location.substring(0, index); + String methodName = location.substring(index + 1); + + boolean found = false; + while (!found) { + Thread.State state = target.getState(); + assertTrue(state != TERMINATED); + if ((state == WAITING || state == TIMED_WAITING) + && contains(target.getStackTrace(), className, methodName)) { + found = true; + } else { + Thread.sleep(20); + } + } + target.interrupt(); + } + + /** + * Schedules the current thread to be interrupted when it waits (timed or untimed) + * at the given location. + */ + private void scheduleInterruptAt(String location) { + Thread target = Thread.currentThread(); + scheduler.submit(() -> { + interruptThreadAt(target, location); + return null; + }); + } + + /** + * Returns true if the given stack trace contains an element for the given class + * and method name. + */ + private boolean contains(StackTraceElement[] stack, String className, String methodName) { + return Arrays.stream(stack) + .anyMatch(e -> className.equals(e.getClassName()) + && methodName.equals(e.getMethodName())); + } +} diff --git a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java similarity index 98% rename from test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java rename to test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java index cc20e743ec3..99129ae3972 100644 --- a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java @@ -23,14 +23,14 @@ /* * @test - * @bug 8284199 + * @bug 8284199 8296779 8306647 * @summary Test thread dumps with StructuredTaskScope - * @modules jdk.incubator.concurrent + * @enablePreview * @library /test/lib * @run junit/othervm StructuredThreadDumpTest */ -import jdk.incubator.concurrent.StructuredTaskScope; +import java.util.concurrent.StructuredTaskScope; import java.io.IOException; import java.lang.management.ManagementFactory; import java.nio.file.Files; diff --git a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/WithScopedValue.java b/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java similarity index 80% rename from test/jdk/jdk/incubator/concurrent/StructuredTaskScope/WithScopedValue.java rename to test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java index 402991285d2..1d6e5e06af3 100644 --- a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/WithScopedValue.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java @@ -23,15 +23,15 @@ /* * @test + * @bug 8284199 8296779 8306647 * @summary Basic tests for StructuredTaskScope with scoped values - * @modules jdk.incubator.concurrent + * @enablePreview * @run junit WithScopedValue */ -import jdk.incubator.concurrent.ScopedValue; -import jdk.incubator.concurrent.StructuredTaskScope; -import jdk.incubator.concurrent.StructureViolationException; -import java.util.concurrent.Future; +import java.util.concurrent.StructuredTaskScope; +import java.util.concurrent.StructuredTaskScope.Subtask; +import java.util.concurrent.StructureViolationException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; @@ -54,13 +54,13 @@ class WithScopedValue { @MethodSource("factories") void testForkInheritsScopedValue1(ThreadFactory factory) throws Exception { ScopedValue name = ScopedValue.newInstance(); - String value = ScopedValue.where(name, "x", () -> { + String value = ScopedValue.callWhere(name, "x", () -> { try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { + Subtask subtask = scope.fork(() -> { return name.get(); // child should read "x" }); scope.join(); - return future.resultNow(); + return subtask.get(); } }); assertEquals(value, "x"); @@ -73,19 +73,19 @@ class WithScopedValue { @MethodSource("factories") void testForkInheritsScopedValue2(ThreadFactory factory) throws Exception { ScopedValue name = ScopedValue.newInstance(); - String value = ScopedValue.where(name, "x", () -> { + String value = ScopedValue.callWhere(name, "x", () -> { try (var scope1 = new StructuredTaskScope(null, factory)) { - Future future1 = scope1.fork(() -> { + Subtask subtask1 = scope1.fork(() -> { try (var scope2 = new StructuredTaskScope(null, factory)) { - Future future2 = scope2.fork(() -> { + Subtask subtask2 = scope2.fork(() -> { return name.get(); // grandchild should read "x" }); scope2.join(); - return future2.resultNow(); + return subtask2.get(); } }); scope1.join(); - return future1.resultNow(); + return subtask1.get(); } }); assertEquals(value, "x"); @@ -98,19 +98,19 @@ class WithScopedValue { @MethodSource("factories") void testForkInheritsScopedValue3(ThreadFactory factory) throws Exception { ScopedValue name = ScopedValue.newInstance(); - String value = ScopedValue.where(name, "x", () -> { + String value = ScopedValue.callWhere(name, "x", () -> { try (var scope1 = new StructuredTaskScope(null, factory)) { - Future future1 = scope1.fork(() -> { + Subtask subtask1 = scope1.fork(() -> { assertEquals(name.get(), "x"); // child should read "x" // rebind name to "y" - String grandchildValue = ScopedValue.where(name, "y", () -> { + String grandchildValue = ScopedValue.callWhere(name, "y", () -> { try (var scope2 = new StructuredTaskScope(null, factory)) { - Future future2 = scope2.fork(() -> { + Subtask subtask2 = scope2.fork(() -> { return name.get(); // grandchild should read "y" }); scope2.join(); - return future2.resultNow(); + return subtask2.get(); } }); @@ -118,7 +118,7 @@ class WithScopedValue { return grandchildValue; }); scope1.join(); - return future1.resultNow(); + return subtask1.get(); } }); assertEquals(value, "y"); @@ -136,23 +136,22 @@ class WithScopedValue { var box = new Box(); try { try { - ScopedValue.where(name, "x", () -> { + ScopedValue.runWhere(name, "x", () -> { box.scope = new StructuredTaskScope(); }); fail(); } catch (StructureViolationException expected) { } - // underlying flock should be closed, fork should return a cancelled task + // underlying flock should be closed and fork should fail to start a thread StructuredTaskScope scope = box.scope; AtomicBoolean ran = new AtomicBoolean(); - Future future = scope.fork(() -> { + Subtask subtask = scope.fork(() -> { ran.set(true); return null; }); - assertTrue(future.isCancelled()); scope.join(); + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); assertFalse(ran.get()); - } finally { StructuredTaskScope scope = box.scope; if (scope != null) { @@ -168,7 +167,7 @@ class WithScopedValue { void testStructureViolation2() throws Exception { ScopedValue name = ScopedValue.newInstance(); try (var scope = new StructuredTaskScope()) { - ScopedValue.where(name, "x", () -> { + ScopedValue.runWhere(name, "x", () -> { assertThrows(StructureViolationException.class, scope::close); }); } @@ -181,7 +180,7 @@ class WithScopedValue { void testStructureViolation3() throws Exception { ScopedValue name = ScopedValue.newInstance(); try (var scope = new StructuredTaskScope()) { - ScopedValue.where(name, "x", () -> { + ScopedValue.runWhere(name, "x", () -> { assertThrows(StructureViolationException.class, () -> scope.fork(() -> "foo")); }); @@ -197,9 +196,9 @@ class WithScopedValue { ScopedValue name2 = ScopedValue.newInstance(); // rebind - ScopedValue.where(name1, "x", () -> { + ScopedValue.runWhere(name1, "x", () -> { try (var scope = new StructuredTaskScope()) { - ScopedValue.where(name1, "y", () -> { + ScopedValue.runWhere(name1, "y", () -> { assertThrows(StructureViolationException.class, () -> scope.fork(() -> "foo")); }); @@ -207,9 +206,9 @@ class WithScopedValue { }); // new binding - ScopedValue.where(name1, "x", () -> { + ScopedValue.runWhere(name1, "x", () -> { try (var scope = new StructuredTaskScope()) { - ScopedValue.where(name2, "y", () -> { + ScopedValue.runWhere(name2, "y", () -> { assertThrows(StructureViolationException.class, () -> scope.fork(() -> "foo")); }); diff --git a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java b/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java deleted file mode 100644 index 59d84b1bfdf..00000000000 --- a/test/jdk/jdk/incubator/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java +++ /dev/null @@ -1,1305 +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. - * - * 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 - * @summary Basic tests for StructuredTaskScope - * @modules jdk.incubator.concurrent - * @run junit/othervm -DthreadFactory=platform StructuredTaskScopeTest - */ - -/* - * @test id=virtual - * @modules jdk.incubator.concurrent - * @run junit/othervm -DthreadFactory=virtual StructuredTaskScopeTest - */ - -import jdk.incubator.concurrent.StructuredTaskScope; -import jdk.incubator.concurrent.StructuredTaskScope.ShutdownOnSuccess; -import jdk.incubator.concurrent.StructuredTaskScope.ShutdownOnFailure; -import jdk.incubator.concurrent.StructureViolationException; -import java.time.Duration; -import java.io.IOException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.NoSuchElementException; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CopyOnWriteArrayList; -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.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -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 each fork creates a thread. - */ - @ParameterizedTest - @MethodSource("factories") - void testFork1(ThreadFactory factory) throws Exception { - AtomicInteger count = new AtomicInteger(); - try (var scope = new StructuredTaskScope(null, factory)) { - for (int i = 0; i < 100; i++) { - scope.fork(() -> count.incrementAndGet()); - } - scope.join(); - } - assertTrue(count.get() == 100); - } - - /** - * Test that fork uses the specified thread factory. - */ - @ParameterizedTest - @MethodSource("factories") - void testFork2(ThreadFactory factory) throws Exception { - AtomicInteger 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(); - } - assertTrue(count.get() == 100); - } - - /** - * 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 - Future future1 = scope1.fork(() -> { - scope2.fork(() -> null).get(); - return null; - }); - Throwable ex = assertThrows(ExecutionException.class, future1::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - - // thread in scope2 can fork thread in scope1 - Future future2 = scope2.fork(() -> { - scope1.fork(() -> null).get(); - return null; - }); - future2.get(); - assertNull(future2.resultNow()); - - // random thread cannot fork - try (var pool = Executors.newCachedThreadPool(factory)) { - Future future = pool.submit(() -> { - scope1.fork(() -> null); - return null; - }); - ex = assertThrows(ExecutionException.class, future::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - } - - scope2.join(); - scope1.join(); - } - } - - /** - * Test fork when scope is shutdown. - */ - @ParameterizedTest - @MethodSource("factories") - void testForkAfterShutdown(ThreadFactory factory) throws Exception { - AtomicInteger count = new AtomicInteger(); - try (var scope = new StructuredTaskScope(null, factory)) { - scope.shutdown(); - Future future = scope.fork(() -> { - count.incrementAndGet(); - return "foo"; - }); - assertTrue(future.isCancelled()); - scope.join(); - } - assertTrue(count.get() == 0); // check that task did not run. - } - - /** - * Test fork when scope is closed. - */ - @ParameterizedTest - @MethodSource("factories") - void testForkAfterClose(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - scope.join(); - scope.close(); - assertThrows(IllegalStateException.class, () -> scope.fork(() -> null)); - } - } - - /** - * Test fork when the thread factory rejects creating a thread. - */ - @Test - void testForkReject() throws Exception { - ThreadFactory factory = task -> null; - try (var scope = new StructuredTaskScope(null, factory)) { - assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); - scope.join(); - } - } - - /** - * A StructuredTaskScope that collects all Future objects notified to the - * handleComplete method. - */ - private static class CollectAll extends StructuredTaskScope { - private final List> futures = new CopyOnWriteArrayList<>(); - - CollectAll(ThreadFactory factory) { - super(null, factory); - } - - @Override - protected void handleComplete(Future future) { - assertTrue(future.isDone()); - futures.add(future); - } - - Stream> futures() { - return futures.stream(); - } - - Set> futuresAsSet() { - return futures.stream().collect(Collectors.toSet()); - } - } - - /** - * Test that handleComplete method is invoked for tasks that complete normally - * and abnormally. - */ - @ParameterizedTest - @MethodSource("factories") - void testHandleComplete1(ThreadFactory factory) throws Exception { - try (var scope = new CollectAll(factory)) { - - // completes normally - Future future1 = scope.fork(() -> "foo"); - - // completes with exception - Future future2 = scope.fork(() -> { throw new FooException(); }); - - // cancelled - Future future3 = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return null; - }); - future3.cancel(true); - - scope.join(); - - Set> futures = scope.futuresAsSet(); - assertEquals(Set.of(future1, future2, future3), futures); - } - } - - /** - * Test that the handeComplete method is not invoked after the scope has been shutdown. - */ - @ParameterizedTest - @MethodSource("factories") - void testHandleComplete2(ThreadFactory factory) throws Exception { - try (var scope = new CollectAll(factory)) { - var latch = new CountDownLatch(1); - - // start task that does not respond to interrupt - Future future1 = scope.fork(() -> { - boolean done = false; - while (!done) { - try { - latch.await(); - done = true; - } catch (InterruptedException e) { } - } - return null; - }); - - // start a second task to shutdown the scope after a short delay - Future future2 = scope.fork(() -> { - Thread.sleep(Duration.ofMillis(100)); - scope.shutdown(); - return null; - }); - - scope.join(); - - // let task finish - latch.countDown(); - - // handleComplete should not have been called - assertTrue(future1.isDone()); - assertTrue(scope.futures().count() == 0L); - } - } - - /** - * Test join with no threads. - */ - @Test - void testJoinWithNoThreads() throws Exception { - try (var scope = new StructuredTaskScope()) { - scope.join(); - } - } - - /** - * Test join with threads running. - */ - @ParameterizedTest - @MethodSource("factories") - void testJoinWithThreads(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofMillis(50)); - return "foo"; - }); - scope.join(); - assertEquals("foo", future.resultNow()); - } - } - - /** - * Test join is owner confined. - */ - @ParameterizedTest - @MethodSource("factories") - void testJoinConfined(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope()) { - // attempt to join on thread in scope - Future future1 = scope.fork(() -> { - scope.join(); - return null; - }); - Throwable ex = assertThrows(ExecutionException.class, future1::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - - // random thread cannot join - try (var pool = Executors.newCachedThreadPool(factory)) { - Future future2 = pool.submit(() -> { - scope.join(); - return null; - }); - ex = assertThrows(ExecutionException.class, future2::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - } - - scope.join(); - } - } - - /** - * 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); - - Future future = 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", future.resultNow()); - } - } - - /** - * 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); - - Future future = scope.fork(() -> { - latch.await(); - return "foo"; - }); - - // join should throw - scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); - 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", future.resultNow()); - } - } - - /** - * Test join when scope is already shutdown. - */ - @ParameterizedTest - @MethodSource("factories") - void testJoinWithShutdown1(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return "foo"; - }); - scope.shutdown(); // interrupts task - scope.join(); - - // task should have completed abnormally - assertTrue(future.isDone() && future.state() != Future.State.SUCCESS); - } - } - - /** - * 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(Future future) { - shutdown(); - } - } - - try (var scope = new MyScope(factory)) { - Future future1 = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return "foo"; - }); - Future future2 = scope.fork(() -> { - Thread.sleep(Duration.ofMillis(50)); - return null; - }); - scope.join(); - - // task1 should have completed abnormally - assertTrue(future1.isDone() && future1.state() != Future.State.SUCCESS); - - // task2 should have completed normally - assertTrue(future2.isDone() && future2.state() == Future.State.SUCCESS); - } - } - - /** - * Test join after scope is shutdown. - */ - @Test - void testJoinAfterShutdown() throws Exception { - try (var scope = new StructuredTaskScope()) { - scope.shutdown(); - scope.join(); - } - } - - /** - * 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, threads finish before deadline expires. - */ - @ParameterizedTest - @MethodSource("factories") - void testJoinUntil1(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { - try { - Thread.sleep(Duration.ofSeconds(2)); - } catch (InterruptedException e) { } - return null; - }); - - long startMillis = millisTime(); - scope.joinUntil(Instant.now().plusSeconds(30)); - assertTrue(future.isDone()); - assertNull(future.resultNow()); - expectDuration(startMillis, /*min*/1900, /*max*/20_000); - } - } - - /** - * Test joinUntil, deadline expires before threads finish. - */ - @ParameterizedTest - @MethodSource("factories") - void testJoinUntil2(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { - try { - Thread.sleep(Duration.ofSeconds(30)); - } catch (InterruptedException e) { } - return null; - }); - - long startMillis = millisTime(); - try { - scope.joinUntil(Instant.now().plusSeconds(2)); - } catch (TimeoutException e) { - expectDuration(startMillis, /*min*/1900, /*max*/20_000); - } - assertFalse(future.isDone()); - } - } - - /** - * Test joinUntil many times. - */ - @ParameterizedTest - @MethodSource("factories") - void testJoinUntil3(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { - try { - Thread.sleep(Duration.ofSeconds(30)); - } catch (InterruptedException e) { } - return null; - }); - - try { - for (int i = 0; i < 3; i++) { - try { - scope.joinUntil(Instant.now().plusMillis(50)); - fail("joinUntil did not throw"); - } catch (TimeoutException expected) { - assertFalse(future.isDone()); - } - } - } finally { - future.cancel(true); - } - } - } - - /** - * 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)) { - Future future = scope.fork(() -> { - try { - Thread.sleep(Duration.ofSeconds(30)); - } catch (InterruptedException e) { } - return null; - }); - - try { - - // now - try { - scope.joinUntil(Instant.now()); - fail("joinUntil did not throw"); - } catch (TimeoutException expected) { - assertFalse(future.isDone()); - } - - // in the past - try { - scope.joinUntil(Instant.now().minusSeconds(1)); - fail("joinUntil did not throw"); - } catch (TimeoutException expected) { - assertFalse(future.isDone()); - } - - } finally { - future.cancel(true); - } - } - } - - /** - * 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); - - Future future = 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", future.resultNow()); - } - } - - /** - * 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); - - Future future = scope.fork(() -> { - latch.await(); - return "foo"; - }); - - // joinUntil should throw - scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); - try { - scope.joinUntil(Instant.now().plusSeconds(10)); - 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", future.resultNow()); - } - } - - /** - * 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()) { - - // random thread cannot shutdown - try (var pool = Executors.newCachedThreadPool(factory)) { - Future future = pool.submit(() -> { - scope1.shutdown(); - return null; - }); - Throwable ex = assertThrows(ExecutionException.class, future::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - } - - // thread in scope1 cannot shutdown scope2 - Future future1 = scope1.fork(() -> { - scope2.shutdown(); - return null; - }); - Throwable ex = assertThrows(ExecutionException.class, future1::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - - // thread in scope2 can shutdown scope1 - Future future2 = scope2.fork(() -> { - scope1.shutdown(); - return null; - }); - future2.get(); - assertNull(future2.resultNow()); - - scope2.join(); - scope1.join(); - } - } - - /** - * Test close without join, no threads forked. - */ - @Test - void testCloseWithoutJoin1() { - try (var scope = new StructuredTaskScope()) { - // do nothing - } - } - - /** - * Test close without join, threads forked. - */ - @ParameterizedTest - @MethodSource("factories") - void testCloseWithoutJoin2(ThreadFactory factory) { - try (var scope = new StructuredTaskScope(null, factory)) { - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return null; - }); - assertThrows(IllegalStateException.class, scope::close); - assertTrue(future.isDone() && future.exceptionNow() != null); - } - } - - /** - * Test close with threads forked after join. - */ - @ParameterizedTest - @MethodSource("factories") - void testCloseWithoutJoin3(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - scope.fork(() -> "foo"); - scope.join(); - - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return null; - }); - assertThrows(IllegalStateException.class, scope::close); - assertTrue(future.isDone() && future.exceptionNow() != null); - } - } - - /** - * Test close is owner confined. - */ - @ParameterizedTest - @MethodSource("factories") - void testCloseConfined(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope()) { - // attempt to close on thread in scope - Future future1 = scope.fork(() -> { - scope.close(); - return null; - }); - Throwable ex = assertThrows(ExecutionException.class, future1::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - - // random thread cannot close scope - try (var pool = Executors.newCachedThreadPool(factory)) { - Future future2 = pool.submit(() -> { - scope.close(); - return null; - }); - ex = assertThrows(ExecutionException.class, future2::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - } - - scope.join(); - } - } - - /** - * Test close with interrupt status set. - */ - @ParameterizedTest - @MethodSource("factories") - void testInterruptClose1(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - var latch = new CountDownLatch(1); - - // start task that does not respond to interrupt - scope.fork(() -> { - boolean done = false; - while (!done) { - try { - latch.await(); - done = true; - } catch (InterruptedException e) { } - } - return null; - }); - - scope.shutdown(); - scope.join(); - - // release task after a delay - scheduler.schedule(latch::countDown, 100, TimeUnit.MILLISECONDS); - - // invoke close with interrupt status set - Thread.currentThread().interrupt(); - try { - scope.close(); - } finally { - assertTrue(Thread.interrupted()); // clear interrupt status - } - } - } - - /** - * Test interrupting thread waiting in close. - */ - @ParameterizedTest - @MethodSource("factories") - void testInterruptClose2(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - var latch = new CountDownLatch(1); - - // start task that does not respond to interrupt - scope.fork(() -> { - boolean done = false; - while (!done) { - try { - latch.await(); - done = true; - } catch (InterruptedException e) { } - } - return null; - }); - - scope.shutdown(); - scope.join(); - - // release task after a delay - scheduleInterrupt(Thread.currentThread(), Duration.ofMillis(500)); - scheduler.schedule(latch::countDown, 3, TimeUnit.SECONDS); - try { - scope.close(); - } finally { - assertTrue(Thread.interrupted()); // clear interrupt status - } - } - } - - /** - * Test that closing an enclosing scope closes the thread flock of a - * nested scope. - */ - @Test - void testStructureViolation1() throws Exception { - try (var scope1 = new StructuredTaskScope()) { - try (var scope2 = new StructuredTaskScope()) { - - // join + close enclosing scope - scope1.join(); - try { - scope1.close(); - fail("close did not throw"); - } catch (StructureViolationException expected) { } - - // underlying flock should be closed, fork should return a cancelled task - AtomicBoolean ran = new AtomicBoolean(); - Future future = scope2.fork(() -> { - ran.set(true); - return null; - }); - assertTrue(future.isCancelled()); - scope2.join(); - assertFalse(ran.get()); - } - } - } - - /** - * Test Future::get, task completes normally. - */ - @ParameterizedTest - @MethodSource("factories") - void testFuture1(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofMillis(20)); - return "foo"; - }); - - assertEquals("foo", future.get()); - assertTrue(future.state() == Future.State.SUCCESS); - assertEquals("foo", future.resultNow()); - - scope.join(); - } - } - - /** - * Test Future::get, task completes with exception. - */ - @ParameterizedTest - @MethodSource("factories") - void testFuture2(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofMillis(20)); - throw new FooException(); - }); - - Throwable ex = assertThrows(ExecutionException.class, future::get); - assertTrue(ex.getCause() instanceof FooException); - assertTrue(future.state() == Future.State.FAILED); - assertTrue(future.exceptionNow() instanceof FooException); - - scope.join(); - } - } - - /** - * Test Future::get, task is cancelled. - */ - @ParameterizedTest - @MethodSource("factories") - void testFuture3(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return null; - }); - - // timed-get, should timeout - try { - future.get(20, TimeUnit.MILLISECONDS); - fail("Future.get did not throw"); - } catch (TimeoutException expected) { } - - future.cancel(true); - assertThrows(CancellationException.class, future::get); - assertTrue(future.state() == Future.State.CANCELLED); - - scope.join(); - } - } - - /** - * Test scope shutdown with a thread blocked in Future::get. - */ - @ParameterizedTest - @MethodSource("factories") - void testFutureWithShutdown(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope(null, factory)) { - - // long running task - Future future = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return null; - }); - - // start a thread to wait in Future::get - AtomicBoolean waitDone = new AtomicBoolean(); - Thread waiter = Thread.startVirtualThread(() -> { - try { - future.get(); - } catch (ExecutionException | CancellationException e) { - waitDone.set(true); - } catch (InterruptedException e) { - System.out.println("waiter thread interrupted!"); - } - }); - - // shutdown scope - scope.shutdown(); - - // Future should be done and thread should be awakened - assertTrue(future.isDone()); - waiter.join(); - assertTrue(waitDone.get()); - - scope.join(); - } - } - - /** - * Test Future::cancel throws if invoked by a thread that is not in the tree. - */ - @ParameterizedTest - @MethodSource("factories") - void testFutureCancelConfined(ThreadFactory factory) throws Exception { - try (var scope = new StructuredTaskScope()) { - Future future1 = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return "foo"; - }); - - // random thread cannot cancel - try (var pool = Executors.newCachedThreadPool(factory)) { - Future future2 = pool.submit(() -> { - future1.cancel(true); - return null; - }); - Throwable ex = assertThrows(ExecutionException.class, future2::get); - assertTrue(ex.getCause() instanceof WrongThreadException); - } finally { - future1.cancel(true); - } - scope.join(); - } - } - - /** - * Test StructuredTaskScope::toString includes the scope name. - */ - @Test - void testToString() throws Exception { - ThreadFactory factory = Thread.ofVirtual().factory(); - try (var scope = new StructuredTaskScope("xxx", factory)) { - // open - assertTrue(scope.toString().contains("xxx")); - - // shutdown - scope.shutdown(); - assertTrue(scope.toString().contains("xxx")); - - // closed - scope.join(); - scope.close(); - assertTrue(scope.toString().contains("xxx")); - } - } - - /** - * Test for NullPointerException. - */ - @Test - void testNulls() throws Exception { - assertThrows(NullPointerException.class, () -> new StructuredTaskScope("", null)); - try (var scope = new StructuredTaskScope()) { - assertThrows(NullPointerException.class, () -> scope.fork(null)); - assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); - } - - assertThrows(NullPointerException.class, () -> new ShutdownOnSuccess("", null)); - try (var scope = new ShutdownOnSuccess()) { - assertThrows(NullPointerException.class, () -> scope.fork(null)); - assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); - assertThrows(NullPointerException.class, () -> scope.result(null)); - } - - assertThrows(NullPointerException.class, () -> new ShutdownOnFailure("", null)); - try (var scope = new ShutdownOnFailure()) { - assertThrows(NullPointerException.class, () -> scope.fork(null)); - assertThrows(NullPointerException.class, () -> scope.joinUntil(null)); - assertThrows(NullPointerException.class, () -> scope.throwIfFailed(null)); - } - } - - /** - * Test ShutdownOnSuccess with no completed tasks. - */ - @Test - void testShutdownOnSuccess1() throws Exception { - try (var scope = new ShutdownOnSuccess()) { - assertThrows(IllegalStateException.class, () -> scope.result()); - assertThrows(IllegalStateException.class, () -> scope.result(e -> null)); - } - } - - /** - * Test ShutdownOnSuccess with tasks that completed normally. - */ - @Test - void testShutdownOnSuccess2() throws Exception { - try (var scope = new ShutdownOnSuccess()) { - - // two tasks complete normally - scope.fork(() -> "foo"); - scope.join(); // ensures foo completes first - scope.fork(() -> "bar"); - scope.join(); - - assertEquals("foo", scope.result()); - assertEquals("foo", scope.result(e -> null)); - } - } - - /** - * Test ShutdownOnSuccess with tasks that completed normally and abnormally. - */ - @Test - void testShutdownOnSuccess3() throws Exception { - try (var scope = new ShutdownOnSuccess()) { - - // one task completes normally, the other with an exception - scope.fork(() -> "foo"); - scope.fork(() -> { throw new ArithmeticException(); }); - scope.join(); - - assertEquals("foo", scope.result()); - assertEquals("foo", scope.result(e -> null)); - } - } - - /** - * Test ShutdownOnSuccess with a task that completed with an exception. - */ - @Test - void testShutdownOnSuccess4() throws Exception { - try (var scope = new ShutdownOnSuccess()) { - - // tasks completes with exception - scope.fork(() -> { throw new ArithmeticException(); }); - scope.join(); - - Throwable ex = assertThrows(ExecutionException.class, () -> scope.result()); - assertTrue(ex.getCause() instanceof ArithmeticException); - - ex = assertThrows(FooException.class, () -> scope.result(e -> new FooException(e))); - assertTrue(ex.getCause() instanceof ArithmeticException); - } - } - - /** - * Test ShutdownOnSuccess with a cancelled task. - */ - @Test - void testShutdownOnSuccess5() throws Exception { - try (var scope = new ShutdownOnSuccess()) { - - // cancelled task - var future = scope.fork(() -> { - Thread.sleep(60_000); - return null; - }); - future.cancel(false); - - scope.join(); - - assertThrows(CancellationException.class, () -> scope.result()); - Throwable ex = assertThrows(FooException.class, - () -> scope.result(e -> new FooException(e))); - assertTrue(ex.getCause() instanceof CancellationException); - } - } - - /** - * Test ShutdownOnFailure with no completed tasks. - */ - @Test - void testShutdownOnFailure1() throws Throwable { - try (var scope = new ShutdownOnFailure()) { - assertTrue(scope.exception().isEmpty()); - scope.throwIfFailed(); - scope.throwIfFailed(e -> new FooException(e)); - } - } - - /** - * Test ShutdownOnFailure with tasks that completed normally. - */ - @Test - void testShutdownOnFailure2() throws Throwable { - try (var scope = new ShutdownOnFailure()) { - scope.fork(() -> "foo"); - scope.fork(() -> "bar"); - scope.join(); - - // no exception - assertTrue(scope.exception().isEmpty()); - scope.throwIfFailed(); - scope.throwIfFailed(e -> new FooException(e)); - } - } - - /** - * Test ShutdownOnFailure with tasks that completed normally and abnormally. - */ - @Test - void testShutdownOnFailure3() throws Throwable { - try (var scope = new ShutdownOnFailure()) { - - // one task completes normally, the other with an exception - scope.fork(() -> "foo"); - scope.fork(() -> { throw new ArithmeticException(); }); - scope.join(); - - Throwable ex = scope.exception().orElse(null); - assertTrue(ex instanceof ArithmeticException); - - ex = assertThrows(ExecutionException.class, () -> scope.throwIfFailed()); - assertTrue(ex.getCause() instanceof ArithmeticException); - - ex = assertThrows(FooException.class, - () -> scope.throwIfFailed(e -> new FooException(e))); - assertTrue(ex.getCause() instanceof ArithmeticException); - } - } - - /** - * Test ShutdownOnFailure with a cancelled task. - */ - @Test - void testShutdownOnFailure4() throws Throwable { - try (var scope = new ShutdownOnFailure()) { - - var future = scope.fork(() -> { - Thread.sleep(60_000); - return null; - }); - future.cancel(false); - - scope.join(); - - Throwable ex = scope.exception().orElse(null); - assertTrue(ex instanceof CancellationException); - - assertThrows(CancellationException.class, () -> scope.throwIfFailed()); - - ex = assertThrows(FooException.class, - () -> scope.throwIfFailed(e -> new FooException(e))); - assertTrue(ex.getCause() instanceof CancellationException); - } - } - - /** - * A runtime exception for tests. - */ - private static class FooException extends RuntimeException { - FooException() { } - FooException(Throwable cause) { super(cause); } - } - - /** - * Schedules a thread to be interrupted after the given delay. - */ - private void scheduleInterrupt(Thread thread, Duration delay) { - long millis = delay.toMillis(); - scheduler.schedule(thread::interrupt, millis, TimeUnit.MILLISECONDS); - } - - /** - * Returns the current time in milliseconds. - */ - private static long millisTime() { - long now = System.nanoTime(); - return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); - } - - /** - * Check the duration of a task - * @param start start time, in milliseconds - * @param min minimum expected duration, in milliseconds - * @param max maximum expected duration, in milliseconds - * @return the duration (now - start), in milliseconds - */ - private static long expectDuration(long start, long min, long max) { - long duration = millisTime() - start; - assertTrue(duration >= min, - "Duration " + duration + "ms, expected >= " + min + "ms"); - assertTrue(duration <= max, - "Duration " + duration + "ms, expected <= " + max + "ms"); - return duration; - } -} diff --git a/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java b/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java index 458d4160705..2f1da20e052 100644 --- a/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java +++ b/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java @@ -1042,7 +1042,7 @@ class ThreadFlockTest { @Test void testToString() { try (var flock = ThreadFlock.open("xxxx")) { - assertTrue(flock.toString().contains("xxx")); + assertTrue(flock.toString().contains("xxxx")); } } diff --git a/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java b/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java index 97cf574d911..f2bfd241722 100644 --- a/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java +++ b/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java @@ -24,15 +24,14 @@ /* * @test * @summary Test ThreadFlock with scoped values + * @enablePreview * @modules java.base/jdk.internal.misc - * @modules jdk.incubator.concurrent * @run junit WithScopedValue */ import jdk.internal.misc.ThreadFlock; -import jdk.incubator.concurrent.ScopedValue; -import jdk.incubator.concurrent.StructureViolationException; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.StructureViolationException; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @@ -54,7 +53,7 @@ class WithScopedValue { @MethodSource("factories") void testInheritsScopedValue(ThreadFactory factory) throws Exception { ScopedValue name = ScopedValue.newInstance(); - String value = ScopedValue.where(name, "duke", () -> { + String value = ScopedValue.callWhere(name, "duke", () -> { var result = new AtomicReference(); try (var flock = ThreadFlock.open(null)) { Thread thread = factory.newThread(() -> { @@ -80,7 +79,7 @@ class WithScopedValue { } var box = new Box(); try { - ScopedValue.where(name, "x1", () -> { + ScopedValue.runWhere(name, "x1", () -> { box.flock1 = ThreadFlock.open(null); box.flock2 = ThreadFlock.open(null); }); @@ -98,11 +97,11 @@ class WithScopedValue { void testStructureViolation2() { ScopedValue name = ScopedValue.newInstance(); try (var flock1 = ThreadFlock.open("flock1")) { - ScopedValue.where(name, "x1", () -> { + ScopedValue.runWhere(name, "x1", () -> { try (var flock2 = ThreadFlock.open("flock2")) { - ScopedValue.where(name, "x2", () -> { + ScopedValue.runWhere(name, "x2", () -> { try (var flock3 = ThreadFlock.open("flock3")) { - ScopedValue.where(name, "x3", () -> { + ScopedValue.runWhere(name, "x3", () -> { var flock4 = ThreadFlock.open("flock4"); try { @@ -130,11 +129,11 @@ class WithScopedValue { void testStructureViolation3() { ScopedValue name = ScopedValue.newInstance(); try (var flock1 = ThreadFlock.open("flock1")) { - ScopedValue.where(name, "x1", () -> { + ScopedValue.runWhere(name, "x1", () -> { try (var flock2 = ThreadFlock.open("flock2")) { - ScopedValue.where(name, "x2", () -> { + ScopedValue.runWhere(name, "x2", () -> { try (var flock3 = ThreadFlock.open("flock3")) { - ScopedValue.where(name, "x3", () -> { + ScopedValue.runWhere(name, "x3", () -> { var flock4 = ThreadFlock.open("flock4"); try { @@ -162,11 +161,11 @@ class WithScopedValue { void testStructureViolation4() { ScopedValue name = ScopedValue.newInstance(); try (var flock1 = ThreadFlock.open("flock1")) { - ScopedValue.where(name, "x1", () -> { + ScopedValue.runWhere(name, "x1", () -> { try (var flock2 = ThreadFlock.open("flock2")) { - ScopedValue.where(name, "x2", () -> { + ScopedValue.runWhere(name, "x2", () -> { try (var flock3 = ThreadFlock.open("flock3")) { - ScopedValue.where(name, "x3", () -> { + ScopedValue.runWhere(name, "x3", () -> { var flock4 = ThreadFlock.open("flock4"); try { @@ -194,7 +193,7 @@ class WithScopedValue { void testStructureViolation5(ThreadFactory factory) throws Exception { ScopedValue name = ScopedValue.newInstance(); try (var flock = ThreadFlock.open(null)) { - ScopedValue.where(name, "duke", () -> { + ScopedValue.runWhere(name, "duke", () -> { Thread thread = factory.newThread(() -> { }); assertThrows(StructureViolationException.class, () -> flock.start(thread)); }); @@ -208,9 +207,9 @@ class WithScopedValue { @MethodSource("factories") void testStructureViolation6(ThreadFactory factory) throws Exception { ScopedValue name = ScopedValue.newInstance(); - ScopedValue.where(name, "duke", () -> { + ScopedValue.runWhere(name, "duke", () -> { try (var flock = ThreadFlock.open(null)) { - ScopedValue.where(name, "duchess", () -> { + ScopedValue.runWhere(name, "duchess", () -> { Thread thread = factory.newThread(() -> { }); assertThrows(StructureViolationException.class, () -> flock.start(thread)); }); diff --git a/test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValues.java b/test/micro/org/openjdk/bench/java/lang/ScopedValues.java similarity index 91% rename from test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValues.java rename to test/micro/org/openjdk/bench/java/lang/ScopedValues.java index ab08dc8919c..69ce3d63fe4 100644 --- a/test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValues.java +++ b/test/micro/org/openjdk/bench/java/lang/ScopedValues.java @@ -22,14 +22,15 @@ */ -package org.openjdk.bench.jdk.incubator.concurrent; +package org.openjdk.bench.java.lang; -import jdk.incubator.concurrent.ScopedValue; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; -import static org.openjdk.bench.jdk.incubator.concurrent.ScopedValuesData.*; +import static org.openjdk.bench.java.lang.ScopedValuesData.*; /** * Tests ScopedValue @@ -40,10 +41,9 @@ import static org.openjdk.bench.jdk.incubator.concurrent.ScopedValuesData.*; @Measurement(iterations=10, time=1) @Threads(1) @Fork(value = 1, - jvmArgsPrepend = {"-Djmh.executor.class=org.openjdk.bench.jdk.incubator.concurrent.ScopedValuesExecutorService", + jvmArgsPrepend = {"-Djmh.executor.class=org.openjdk.bench.java.lang.ScopedValuesExecutorService", "-Djmh.executor=CUSTOM", "-Djmh.blackhole.mode=COMPILER", - "--add-modules=jdk.incubator.concurrent", "--enable-preview"}) @State(Scope.Thread) @SuppressWarnings("preview") @@ -161,12 +161,21 @@ public class ScopedValues { } // Test 4: The cost of binding, but not using any result - @Benchmark @OutputTimeUnit(TimeUnit.NANOSECONDS) public Object bind_ScopedValue() throws Exception { - return HOLD_42.call(this::getClass); + return HOLD_42.call(aCallable); } + private static final Callable> aCallable = () -> ScopedValues.class; + + // Same, but make sure that Carrier.get(Supplier) is no slower + // than Carrier.call(Callable). + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Object bindViaGet_ScopedValue() { + return HOLD_42.get(aSupplier); + } + private static final Supplier> aSupplier = () -> ScopedValues.class; @Benchmark @OutputTimeUnit(TimeUnit.NANOSECONDS) diff --git a/test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValuesData.java b/test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java similarity index 96% rename from test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValuesData.java rename to test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java index 29e007d3483..693594fbaee 100644 --- a/test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValuesData.java +++ b/test/micro/org/openjdk/bench/java/lang/ScopedValuesData.java @@ -21,9 +21,8 @@ * questions. */ -package org.openjdk.bench.jdk.incubator.concurrent; +package org.openjdk.bench.java.lang; -import jdk.incubator.concurrent.ScopedValue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; diff --git a/test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValuesExecutorService.java b/test/micro/org/openjdk/bench/java/lang/ScopedValuesExecutorService.java similarity index 92% rename from test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValuesExecutorService.java rename to test/micro/org/openjdk/bench/java/lang/ScopedValuesExecutorService.java index 5238dbcc665..cc105191724 100644 --- a/test/micro/org/openjdk/bench/jdk/incubator/concurrent/ScopedValuesExecutorService.java +++ b/test/micro/org/openjdk/bench/java/lang/ScopedValuesExecutorService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -21,8 +21,7 @@ * questions. */ - -package org.openjdk.bench.jdk.incubator.concurrent; +package org.openjdk.bench.java.lang; import java.util.concurrent.*;