Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8331189: Implementation of Scoped Values (Third Preview) #19136

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 46 additions & 101 deletions src/java.base/share/classes/java/lang/ScopedValue.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2024, 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.
*
Expand Down Expand Up @@ -29,7 +29,6 @@
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;
Expand Down Expand Up @@ -64,18 +63,19 @@
* 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.
* with an exception. The {@code ScopedValue} API supports executing a {@link Runnable},
* or {@link CallableOp} with a {@code ScopedValue} bound to a value.
*
* <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}.
* "{@code duke}" for the execution of a {@code Runnable}'s {@code run} method.
* The {@code run} method, in turn, invokes a method {@code doSomething}.
*
*
* {@snippet lang=java :
* // @link substring="newInstance" target="#newInstance" :
* private static final ScopedValue<String> NAME = ScopedValue.newInstance();
*
* // @link substring="runWhere" target="#runWhere" :
* // @link substring="runWhere" target="#runWhere(ScopedValue, Object, Runnable)" :
* ScopedValue.runWhere(NAME, "duke", () -> doSomething());
* }
* Code executed directly or indirectly by {@code doSomething}, with access to the field
Expand All @@ -84,9 +84,8 @@
* the {@code run} method completes.
*
* <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.
* The {@link #callWhere(ScopedValue, Object, CallableOp) callWhere} method 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.
Expand Down Expand Up @@ -143,7 +142,7 @@
* 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.
* {@link StructuredTaskScope#fork(java.util.concurrent.Callable) fork} method.
*
* <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.
Expand Down Expand Up @@ -291,8 +290,8 @@ Object find(ScopedValue<?> key) {
/**
* A mapping of scoped values, as <em>keys</em>, to values.
*
* <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
* <p> A {@code Carrier} is used to accumulate mappings so that an operation (a {@link
* Runnable} or {@link CallableOp}) 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}.
* {@snippet lang=java :
Expand Down Expand Up @@ -383,7 +382,7 @@ public <T> T get(ScopedValue<T> key) {
carrier = carrier.prev) {
if (carrier.getKey() == key) {
Object value = carrier.get();
return (T)value;
return (T) value;
}
}
throw new NoSuchElementException();
Expand All @@ -406,62 +405,21 @@ public <T> T get(ScopedValue<T> key) {
*
* @param op the operation to run
* @param <R> the type of the result of the operation
* @param <X> type of the exception thrown by the operation
* @return the result
* @throws StructureViolationException if a structure violation is detected
* @throws Exception if {@code op} completes with an exception
* @see ScopedValue#callWhere(ScopedValue, Object, Callable)
* @throws X if {@code op} completes with an exception
* @see ScopedValue#callWhere(ScopedValue, Object, CallableOp)
* @since 23
*/
public <R> R call(Callable<? extends R> op) throws Exception {
public <R, X extends Throwable> R call(CallableOp<? extends R, X> op) throws X {
Objects.requireNonNull(op);
Cache.invalidate(bitmask);
var prevSnapshot = scopedValueBindings();
var newSnapshot = new Snapshot(this, prevSnapshot);
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. If {@code op} completes with an
* exception then it propagated by this method.
*
* <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
* invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
* but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
* as a <em>structure violation</em> when the operation completes (normally or with an
* exception). In that case, the underlying construct of the {@code StructuredTaskScope}
* is closed and {@link StructureViolationException} is thrown.
*
* @param op the operation to run
* @param <R> the type of the result of the operation
* @return the result
* @throws StructureViolationException if a structure violation is detected
* @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.
*
Expand All @@ -471,7 +429,7 @@ public V call() {
*/
@Hidden
@ForceInline
private <R> R runWith(Snapshot newSnapshot, Callable<R> op) {
private <R, X extends Throwable> R runWith(Snapshot newSnapshot, CallableOp<R, X> op) {
try {
Thread.setScopedValueBindings(newSnapshot);
Thread.ensureMaterializedForStackWalk(newSnapshot);
Expand Down Expand Up @@ -532,6 +490,24 @@ private void runWith(Snapshot newSnapshot, Runnable op) {
}
}

/**
* An operation that returns a result and may throw an exception.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - should it be "or may throw an exception"?

*
* @param <T> result type of the operation
* @param <X> type of the exception thrown by the operation
* @since 23
*/
@PreviewFeature(feature = PreviewFeature.Feature.SCOPED_VALUES)
@FunctionalInterface
public interface CallableOp<T, X extends Throwable> {
AlanBateman marked this conversation as resolved.
Show resolved Hide resolved
/**
* Executes this operation.
* @return the result, can be null
* @throws X if the operation completes with an exception
*/
T call() throws X;
}

/**
* Creates a new {@code Carrier} with a single mapping of a {@code ScopedValue}
* <em>key</em> to a value. The {@code Carrier} can be used to accumulate mappings so
Expand Down Expand Up @@ -569,58 +545,27 @@ public static <T> Carrier where(ScopedValue<T> key, T value) {
* @implNote
* This method is implemented to be equivalent to:
* {@snippet lang=java :
* // @link substring="call" target="Carrier#call(Callable)" :
* // @link substring="call" target="Carrier#call(CallableOp)" :
* ScopedValue.where(key, value).call(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
* @throws StructureViolationException if a structure violation is detected
* @throws Exception if the operation completes with an exception
*/
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. If {@code op}
* completes with an exception then it propagated by this method.
*
* <p> Scoped values are intended to be used in a <em>structured manner</em>. If code
* invoked directly or indirectly by the operation creates a {@link StructuredTaskScope}
* but does not {@linkplain StructuredTaskScope#close() close} it, then it is detected
* as a <em>structure violation</em> when the operation completes (normally or with an
* exception). In that case, the underlying construct of the {@code StructuredTaskScope}
* is closed and {@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 <X> type of the exception thrown by the operation
* @param op the operation to call
* @return the result
* @throws StructureViolationException if a structure violation is detected
* @throws X if the operation completes with an exception
* @since 23
*/
public static <T, R> R getWhere(ScopedValue<T> key,
T value,
Supplier<? extends R> op) {
return where(key, value).get(op);
public static <T, R, X extends Throwable> R callWhere(ScopedValue<T> key,
T value,
CallableOp<? extends R, X> op) throws X {
return where(key, value).call(op);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ public static <T> T callAs(final Subject subject,
Objects.requireNonNull(action);
if (!SharedSecrets.getJavaLangAccess().allowSecurityManager()) {
try {
return ScopedValue.callWhere(SCOPED_SUBJECT, subject, action);
return ScopedValue.callWhere(SCOPED_SUBJECT, subject, action::call);
} catch (Exception e) {
throw new CompletionException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public enum Feature {
STRING_TEMPLATES,
@JEP(number=477, title="Implicitly Declared Classes and Instance Main Methods", status="Third Preview")
IMPLICIT_CLASSES,
@JEP(number=464, title="Scoped Values", status="Second Preview")
@JEP(number=481, title="Scoped Values", status="Third Preview")
SCOPED_VALUES,
@JEP(number=480, title="Structured Concurrency", status="Third Preview")
STRUCTURED_CONCURRENCY,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, 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
Expand All @@ -24,7 +24,7 @@
*/
package jdk.internal.vm;

import java.util.concurrent.Callable;
import java.lang.ScopedValue.CallableOp;
import java.util.concurrent.StructureViolationException;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
Expand Down Expand Up @@ -141,7 +141,7 @@ private void doRun(Runnable op) {
/**
* For use by ScopedValue to call a value returning operation in a structured context.
*/
public static <V> V call(Callable<V> op) {
public static <V, X extends Throwable> V call(CallableOp<V, X> op) {
if (head() == null) {
// no need to push scope when stack is empty
return callWithoutScope(op);
Expand All @@ -153,7 +153,7 @@ public static <V> V call(Callable<V> op) {
/**
* Call an operation without a scope on the stack.
*/
private static <V> V callWithoutScope(Callable<V> op) {
private static <V, X extends Throwable> V callWithoutScope(CallableOp<V, X> op) {
assert head() == null;
Throwable ex;
boolean atTop;
Expand All @@ -175,7 +175,7 @@ private static <V> V callWithoutScope(Callable<V> op) {
/**
* Call an operation with this scope on the stack.
*/
private <V> V doCall(Callable<V> op) {
private <V, X extends Throwable> V doCall(CallableOp<V, X> op) {
Throwable ex;
boolean atTop;
V result;
Expand Down
Loading