Skip to content

Commit

Permalink
8331189: Implementation of Scoped Values (Third Preview)
Browse files Browse the repository at this point in the history
Reviewed-by: aph, jpai, mcimadamore
  • Loading branch information
Alan Bateman committed May 30, 2024
1 parent 4acafb8 commit 7071542
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 267 deletions.
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.
*
* @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> {
/**
* 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

1 comment on commit 7071542

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.