8306647: Implementation of Structured Concurrency (Preview)
8306572: Implementation of Scoped Values (Preview) Co-authored-by: Alan Bateman <alanb@openjdk.org> Co-authored-by: Andrew Haley <aph@openjdk.org> Reviewed-by: psandoz, dfuchs, mchung
This commit is contained in:
parent
a08c5cb3f1
commit
f1c7afcc3f
@ -42,7 +42,6 @@ DOCS_MODULES= \
|
||||
jdk.hotspot.agent \
|
||||
jdk.httpserver \
|
||||
jdk.jpackage \
|
||||
jdk.incubator.concurrent \
|
||||
jdk.incubator.vector \
|
||||
jdk.jartool \
|
||||
jdk.javadoc \
|
||||
|
@ -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 \
|
||||
|
@ -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") \
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
*
|
||||
* <p> {@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 <b><em>dynamic scope</em></b>. The scoped
|
||||
* value is {@linkplain #isBound() bound} while executing in the dynamic scope, it reverts
|
||||
* to being <em>unbound</em> 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.
|
||||
* <p> 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
|
||||
* <em>callback</em>) without using method parameters. In effect, a {@code ScopedValue}
|
||||
* is an <em>implicit method parameter</em>. 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
|
||||
* <em>caller</em> to a faraway <em>callee</em> through a sequence of intermediate methods
|
||||
* that do not declare a parameter for the data and have no access to the data.
|
||||
*
|
||||
* <p> 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.
|
||||
* <p> The {@code ScopedValue} API works by executing a method with a {@code ScopedValue}
|
||||
* object <em>bound</em> 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 <em>dynamic scope</em>. Code in these methods with
|
||||
* access to the {@code ScopedValue} object may read its value. The {@code ScopedValue}
|
||||
* object reverts to being <em>unbound</em> 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.
|
||||
*
|
||||
* <p> Consider the following example with a scoped value {@code USERNAME} that is
|
||||
* <em>bound</em> to the value "{@code duke}" for the execution, by a thread, of a run
|
||||
* method that invokes {@code doSomething()}.
|
||||
* <p> 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<String> USERNAME = ScopedValue.newInstance();
|
||||
* private static final ScopedValue<String> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
* <h2>Bindings are per-thread</h2>
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <h2>Scoped values as capabilities</h2>
|
||||
*
|
||||
* A {@code ScopedValue} object should be treated as a <em>capability</em> or a key to
|
||||
* access its value when the {@code ScopedValue} is bound. Secure usage depends on access
|
||||
* control (see <cite>The Java Virtual Machine Specification</cite>, 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).
|
||||
*
|
||||
* <h2><a id="rebind">Rebinding</a></h2>
|
||||
*
|
||||
* The {@code ScopedValue} API allows a new binding to be established for <em>nested
|
||||
* dynamic scopes</em>. This is known as <em>rebinding</em>. 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.
|
||||
*
|
||||
* <p> In the above example, suppose that code executed by {@code doSomething()} binds
|
||||
* {@code USERNAME} to a new value with:
|
||||
* <p> 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}".
|
||||
*
|
||||
* <h2><a id="inheritance">Inheritance</a></h2>
|
||||
*
|
||||
* {@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 <em>captured</em> 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 <em>captured</em> when creating a {@code StructuredTaskScope}
|
||||
* and inherited by all threads started in that task scope with the
|
||||
* {@link StructuredTaskScope#fork(Callable) fork} method.
|
||||
*
|
||||
* <p> In the following example, the {@code ScopedValue} {@code USERNAME} is bound to the
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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<String> USERNAME = ScopedValue.newInstance();
|
||||
* private static final ScopedValue<String> NAME = ScopedValue.newInstance();
|
||||
|
||||
* ScopedValue.where(USERNAME, "duke", () -> {
|
||||
* ScopedValue.runWhere(NAME, "duke", () -> {
|
||||
* try (var scope = new StructuredTaskScope<String>()) {
|
||||
*
|
||||
* scope.fork(() -> childTask1());
|
||||
@ -138,6 +170,23 @@ import sun.security.action.GetPropertyAction;
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* <p> 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:
|
||||
* <ol>
|
||||
* <li> {@code ThreadLocal} does not prevent code in a faraway callee from {@linkplain
|
||||
* ThreadLocal#set(Object) setting} a new value.
|
||||
* <li> A {@code ThreadLocal} has an unbounded lifetime and thus continues to have a value
|
||||
* after a method completes, unless explicitly {@linkplain ThreadLocal#remove() removed}.
|
||||
* <li> {@linkplain InheritableThreadLocal Inheritance} is expensive - the map of
|
||||
* thread-locals to values must be copied when creating each child thread.
|
||||
* </ol>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>For this incubator release, the reference implementation
|
||||
* <p>For this release, the reference implementation
|
||||
* provides some system properties to tune the performance of scoped
|
||||
* values.
|
||||
*
|
||||
* <p>The system property {@code jdk.incubator.concurrent.ScopedValue.cacheSize}
|
||||
* <p>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.
|
||||
*
|
||||
* <p>For example, you could use {@code -Djdk.incubator.concurrent.ScopedValue.cacheSize=8}.
|
||||
* <p>For example, you could use {@code -Djava.lang.ScopedValue.cacheSize=8}.
|
||||
*
|
||||
* <p>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 <T> the type of the object bound to this {@code ScopedValue}
|
||||
* @since 20
|
||||
* @param <T> the type of the value
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.SCOPED_VALUES)
|
||||
public final class ScopedValue<T> {
|
||||
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<T> {
|
||||
/**
|
||||
* A mapping of scoped values, as <em>keys</em>, to values.
|
||||
*
|
||||
* <p> A {@code Carrier} is used to accumlate mappings so that an operation (a
|
||||
* <p> 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<T> {
|
||||
* <p> 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<T> {
|
||||
/**
|
||||
* Add a binding to this map, returning a new Carrier instance.
|
||||
*/
|
||||
private static final <T> Carrier where(ScopedValue<T> key, T value,
|
||||
Carrier prev) {
|
||||
private static <T> Carrier where(ScopedValue<T> key, T value, Carrier prev) {
|
||||
return new Carrier(key, value, prev);
|
||||
}
|
||||
|
||||
@ -311,11 +359,11 @@ public final class ScopedValue<T> {
|
||||
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<T> {
|
||||
* @param <R> 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> R call(Callable<? extends R> op) throws Exception {
|
||||
Objects.requireNonNull(op);
|
||||
@ -370,6 +418,48 @@ public final class ScopedValue<T> {
|
||||
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.
|
||||
*
|
||||
* <p> Scoped values are intended to be used in a <em>structured manner</em>.
|
||||
* 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 <R> the type of the result of the operation
|
||||
* @return the result
|
||||
* @see ScopedValue#getWhere(ScopedValue, Object, Supplier)
|
||||
*/
|
||||
public <R> R get(Supplier<? extends R> op) {
|
||||
Objects.requireNonNull(op);
|
||||
Cache.invalidate(bitmask);
|
||||
var prevSnapshot = scopedValueBindings();
|
||||
var newSnapshot = new Snapshot(this, prevSnapshot);
|
||||
return runWith(newSnapshot, new CallableAdapter<R>(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<V> implements Callable<V> {
|
||||
private /*non-final*/ Supplier<? extends V> s;
|
||||
CallableAdapter(Supplier<? extends V> s) {
|
||||
this.s = s;
|
||||
}
|
||||
public V call() {
|
||||
return s.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the action with a set of ScopedValue bindings.
|
||||
*
|
||||
@ -379,14 +469,14 @@ public final class ScopedValue<T> {
|
||||
*/
|
||||
@Hidden
|
||||
@ForceInline
|
||||
private <R> R runWith(Snapshot newSnapshot, Callable<R> op) throws Exception {
|
||||
private <R> R runWith(Snapshot newSnapshot, Callable<R> 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<T> {
|
||||
* 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<T> {
|
||||
@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<T> {
|
||||
* @return the result
|
||||
* @throws Exception if the operation completes with an exception
|
||||
*/
|
||||
public static <T, R> R where(ScopedValue<T> key,
|
||||
public static <T, R> R callWhere(ScopedValue<T> key,
|
||||
T value,
|
||||
Callable<? extends R> op) throws Exception {
|
||||
return where(key, value).call(op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a supplier of results with a {@code ScopedValue} bound to a value
|
||||
* in the current thread. When the operation completes (normally or with an
|
||||
* exception), the {@code ScopedValue} will revert to being unbound, or revert to
|
||||
* its previous value when previously bound, in the current thread.
|
||||
*
|
||||
* <p> Scoped values are intended to be used in a <em>structured manner</em>.
|
||||
* 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 <T> the type of the value
|
||||
* @param <R> the result type
|
||||
* @param op the operation to call
|
||||
* @return the result
|
||||
*/
|
||||
public static <T, R> R getWhere(ScopedValue<T> key,
|
||||
T value,
|
||||
Supplier<? extends R> op) {
|
||||
return where(key, value).get(op);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an operation with a {@code ScopedValue} bound to a value in the current
|
||||
* thread. When the operation completes (normally or with an exception), the
|
||||
@ -520,7 +644,7 @@ public final class ScopedValue<T> {
|
||||
* @param <T> the type of the value
|
||||
* @param op the operation to call
|
||||
*/
|
||||
public static <T> void where(ScopedValue<T> key, T value, Runnable op) {
|
||||
public static <T> void runWhere(ScopedValue<T> key, T value, Runnable op) {
|
||||
where(key, value).run(op);
|
||||
}
|
||||
|
||||
@ -642,11 +766,11 @@ public final class ScopedValue<T> {
|
||||
}
|
||||
|
||||
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<T> {
|
||||
// 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<T> {
|
||||
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) {
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
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();
|
||||
}
|
||||
|
@ -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> V call(Callable<V> op) throws Exception {
|
||||
public static <V> V call(Callable<V> 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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,13 +217,17 @@ public class ThreadContainers {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
public String name() {
|
||||
return "<root>";
|
||||
}
|
||||
@Override
|
||||
public StackableScope previous() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform threads that are not in the container as these
|
||||
|
@ -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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<ThreadFactory> factories() {
|
||||
return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
|
||||
@ -56,7 +56,7 @@ class ScopeValueAPI {
|
||||
test(factory, () -> {
|
||||
class Box { static boolean executed; }
|
||||
ScopedValue<String> 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<String> 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<String> 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<String> name = ScopedValue.newInstance();
|
||||
String result = ScopedValue.getWhere(name, "duke", (Supplier<String>)(name::get));
|
||||
assertEquals("duke", result);
|
||||
});
|
||||
}
|
||||
@ -99,7 +112,22 @@ class ScopeValueAPI {
|
||||
class FooException extends RuntimeException { }
|
||||
ScopedValue<String> name = ScopedValue.newInstance();
|
||||
Callable<Void> 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<String> name = ScopedValue.newInstance();
|
||||
Supplier<Void> 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<String> 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<String> 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<String> 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<String> 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));
|
||||
}
|
@ -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<Integer> 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<Object>("", 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<Object>()) {
|
||||
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
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
@ -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<String> name = ScopedValue.newInstance();
|
||||
String value = ScopedValue.where(name, "x", () -> {
|
||||
String value = ScopedValue.callWhere(name, "x", () -> {
|
||||
try (var scope = new StructuredTaskScope<String>(null, factory)) {
|
||||
Future<String> future = scope.fork(() -> {
|
||||
Subtask<String> 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<String> name = ScopedValue.newInstance();
|
||||
String value = ScopedValue.where(name, "x", () -> {
|
||||
String value = ScopedValue.callWhere(name, "x", () -> {
|
||||
try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
|
||||
Future<String> future1 = scope1.fork(() -> {
|
||||
Subtask<String> subtask1 = scope1.fork(() -> {
|
||||
try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
|
||||
Future<String> future2 = scope2.fork(() -> {
|
||||
Subtask<String> 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<String> name = ScopedValue.newInstance();
|
||||
String value = ScopedValue.where(name, "x", () -> {
|
||||
String value = ScopedValue.callWhere(name, "x", () -> {
|
||||
try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
|
||||
Future<String> future1 = scope1.fork(() -> {
|
||||
Subtask<String> 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<String>(null, factory)) {
|
||||
Future<String> future2 = scope2.fork(() -> {
|
||||
Subtask<String> 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<Object>();
|
||||
});
|
||||
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<Object> scope = box.scope;
|
||||
AtomicBoolean ran = new AtomicBoolean();
|
||||
Future<Object> future = scope.fork(() -> {
|
||||
Subtask<Object> subtask = scope.fork(() -> {
|
||||
ran.set(true);
|
||||
return null;
|
||||
});
|
||||
assertTrue(future.isCancelled());
|
||||
scope.join();
|
||||
assertEquals(Subtask.State.UNAVAILABLE, subtask.state());
|
||||
assertFalse(ran.get());
|
||||
|
||||
} finally {
|
||||
StructuredTaskScope<Object> scope = box.scope;
|
||||
if (scope != null) {
|
||||
@ -168,7 +167,7 @@ class WithScopedValue {
|
||||
void testStructureViolation2() throws Exception {
|
||||
ScopedValue<String> name = ScopedValue.newInstance();
|
||||
try (var scope = new StructuredTaskScope<String>()) {
|
||||
ScopedValue.where(name, "x", () -> {
|
||||
ScopedValue.runWhere(name, "x", () -> {
|
||||
assertThrows(StructureViolationException.class, scope::close);
|
||||
});
|
||||
}
|
||||
@ -181,7 +180,7 @@ class WithScopedValue {
|
||||
void testStructureViolation3() throws Exception {
|
||||
ScopedValue<String> name = ScopedValue.newInstance();
|
||||
try (var scope = new StructuredTaskScope<String>()) {
|
||||
ScopedValue.where(name, "x", () -> {
|
||||
ScopedValue.runWhere(name, "x", () -> {
|
||||
assertThrows(StructureViolationException.class,
|
||||
() -> scope.fork(() -> "foo"));
|
||||
});
|
||||
@ -197,9 +196,9 @@ class WithScopedValue {
|
||||
ScopedValue<String> name2 = ScopedValue.newInstance();
|
||||
|
||||
// rebind
|
||||
ScopedValue.where(name1, "x", () -> {
|
||||
ScopedValue.runWhere(name1, "x", () -> {
|
||||
try (var scope = new StructuredTaskScope<String>()) {
|
||||
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<String>()) {
|
||||
ScopedValue.where(name2, "y", () -> {
|
||||
ScopedValue.runWhere(name2, "y", () -> {
|
||||
assertThrows(StructureViolationException.class,
|
||||
() -> scope.fork(() -> "foo"));
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<String> name = ScopedValue.newInstance();
|
||||
String value = ScopedValue.where(name, "duke", () -> {
|
||||
String value = ScopedValue.callWhere(name, "duke", () -> {
|
||||
var result = new AtomicReference<String>();
|
||||
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<String> 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<String> 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<String> 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<String> 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<String> 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));
|
||||
});
|
||||
|
@ -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<Class<?>> 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<Class<?>> aSupplier = () -> ScopedValues.class;
|
||||
|
||||
@Benchmark
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
@ -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;
|
||||
|
@ -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.*;
|
||||
|
Loading…
Reference in New Issue
Block a user