8331189: Implementation of Scoped Values (Third Preview)

Reviewed-by: aph, jpai, mcimadamore
This commit is contained in:
Alan Bateman 2024-05-30 15:41:56 +00:00
parent 4acafb809c
commit 707154235b
7 changed files with 96 additions and 268 deletions

View File

@ -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.
*
@ -29,7 +29,6 @@ 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;
@ -64,18 +63,19 @@ import sun.security.action.GetPropertyAction;
* 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
@ -84,9 +84,8 @@ import sun.security.action.GetPropertyAction;
* 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.
@ -143,7 +142,7 @@ import sun.security.action.GetPropertyAction;
* 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.
@ -291,8 +290,8 @@ public final class ScopedValue<T> {
/**
* 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 :
@ -383,7 +382,7 @@ public final class ScopedValue<T> {
carrier = carrier.prev) {
if (carrier.getKey() == key) {
Object value = carrier.get();
return (T)value;
return (T) value;
}
}
throw new NoSuchElementException();
@ -406,12 +405,14 @@ public final class ScopedValue<T> {
*
* @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();
@ -419,49 +420,6 @@ 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. 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.
*
@ -471,7 +429,7 @@ public final class ScopedValue<T> {
*/
@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);
@ -532,6 +490,24 @@ public final class ScopedValue<T> {
}
}
/**
* 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
@ -569,60 +545,29 @@ public final class ScopedValue<T> {
* @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 <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 Exception if the operation completes with an exception
* @throws X if the operation completes with an exception
* @since 23
*/
public static <T, R> R callWhere(ScopedValue<T> key,
T value,
Callable<? extends R> op) throws Exception {
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);
}
/**
* 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 op the operation to call
* @return the result
* @throws StructureViolationException if a structure violation is detected
*/
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

View File

@ -436,7 +436,7 @@ public final class Subject implements java.io.Serializable {
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);
}

View File

@ -71,7 +71,7 @@ public @interface PreviewFeature {
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,

View File

@ -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
@ -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;
@ -141,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) {
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);
@ -153,7 +153,7 @@ public class ScopedValueContainer extends StackableScope {
/**
* 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;
@ -175,7 +175,7 @@ public class ScopedValueContainer extends StackableScope {
/**
* 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;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, 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
@ -28,8 +28,8 @@
* @run junit ScopedValueAPI
*/
import java.lang.ScopedValue.CallableOp;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@ -48,11 +48,11 @@ class ScopedValueAPI {
}
/**
* Test that the run method is invoked.
* Test that runWhere invokes the Runnable's run method.
*/
@ParameterizedTest
@MethodSource("factories")
void testRun(ThreadFactory factory) throws Exception {
void testRunWhere(ThreadFactory factory) throws Exception {
test(factory, () -> {
class Box { static boolean executed; }
ScopedValue<String> name = ScopedValue.newInstance();
@ -62,11 +62,11 @@ class ScopedValueAPI {
}
/**
* Test the run method throwing an exception.
* Test runWhere when the run method throws an exception.
*/
@ParameterizedTest
@MethodSource("factories")
void testRunThrows(ThreadFactory factory) throws Exception {
void testRunWhereThrows(ThreadFactory factory) throws Exception {
test(factory, () -> {
class FooException extends RuntimeException { }
ScopedValue<String> name = ScopedValue.newInstance();
@ -77,11 +77,11 @@ class ScopedValueAPI {
}
/**
* Test that the call method is invoked.
* Test that callWhere invokes the CallableOp's call method.
*/
@ParameterizedTest
@MethodSource("factories")
void testCall(ThreadFactory factory) throws Exception {
void testCallWhere(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
String result = ScopedValue.callWhere(name, "duke", name::get);
@ -90,48 +90,20 @@ class ScopedValueAPI {
}
/**
* Test that the get method is invoked.
* Test callWhere when the call method throws an exception.
*/
@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);
});
}
/**
* Test the call method throwing an exception.
*/
@ParameterizedTest
@MethodSource("factories")
void testCallThrows(ThreadFactory factory) throws Exception {
void testCallWhereThrows(ThreadFactory factory) throws Exception {
test(factory, () -> {
class FooException extends RuntimeException { }
ScopedValue<String> name = ScopedValue.newInstance();
Callable<Void> op = () -> { throw new FooException(); };
CallableOp<Void, RuntimeException> op = () -> { throw new FooException(); };
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());
});
}
/**
* Test get method.
*/
@ -144,7 +116,7 @@ class ScopedValueAPI {
assertThrows(NoSuchElementException.class, name1::get);
assertThrows(NoSuchElementException.class, name2::get);
// run
// runWhere
ScopedValue.runWhere(name1, "duke", () -> {
assertEquals("duke", name1.get());
assertThrows(NoSuchElementException.class, name2::get);
@ -153,7 +125,7 @@ class ScopedValueAPI {
assertThrows(NoSuchElementException.class, name1::get);
assertThrows(NoSuchElementException.class, name2::get);
// call
// callWhere
ScopedValue.callWhere(name1, "duke", () -> {
assertEquals("duke", name1.get());
assertThrows(NoSuchElementException.class, name2::get);
@ -161,15 +133,6 @@ class ScopedValueAPI {
});
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;
});
assertThrows(NoSuchElementException.class, name1::get);
assertThrows(NoSuchElementException.class, name2::get);
});
}
@ -185,7 +148,7 @@ class ScopedValueAPI {
assertFalse(name1.isBound());
assertFalse(name2.isBound());
// run
// runWhere
ScopedValue.runWhere(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
@ -193,16 +156,7 @@ class ScopedValueAPI {
assertFalse(name1.isBound());
assertFalse(name2.isBound());
// call
ScopedValue.callWhere(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
return null;
});
assertFalse(name1.isBound());
assertFalse(name2.isBound());
// call
// callWhere
ScopedValue.callWhere(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
@ -224,13 +178,13 @@ class ScopedValueAPI {
assertNull(name.orElse(null));
assertEquals("default", name.orElse("default"));
// run
// runWhere
ScopedValue.runWhere(name, "duke", () -> {
assertEquals("duke", name.orElse(null));
assertEquals("duke", name.orElse("default"));
});
// call
// callWhere
ScopedValue.callWhere(name, "duke", () -> {
assertEquals("duke", name.orElse(null));
assertEquals("duke", name.orElse("default"));
@ -250,12 +204,12 @@ class ScopedValueAPI {
ScopedValue<String> name = ScopedValue.newInstance();
assertThrows(FooException.class, () -> name.orElseThrow(FooException::new));
// run
// runWhere
ScopedValue.runWhere(name, "duke", () -> {
assertEquals("duke", name.orElseThrow(FooException::new));
});
// call
// callWhere
ScopedValue.callWhere(name, "duke", () -> {
assertEquals("duke", name.orElseThrow(FooException::new));
return null;
@ -273,7 +227,7 @@ class ScopedValueAPI {
ScopedValue<String> name = ScopedValue.newInstance();
ScopedValue<Integer> age = ScopedValue.newInstance();
// run
// Carrier.run
ScopedValue.where(name, "duke").where(age, 100).run(() -> {
assertTrue(name.isBound());
assertTrue(age.isBound());
@ -283,7 +237,7 @@ class ScopedValueAPI {
assertFalse(name.isBound());
assertFalse(age.isBound());
// call
// Carrier.call
ScopedValue.where(name, "duke").where(age, 100).call(() -> {
assertTrue(name.isBound());
assertTrue(age.isBound());
@ -293,18 +247,6 @@ class ScopedValueAPI {
});
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());
});
}
@ -317,7 +259,7 @@ class ScopedValueAPI {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
// run
// runWhere
ScopedValue.runWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
@ -332,7 +274,7 @@ class ScopedValueAPI {
});
assertFalse(name.isBound());
// call
// callWhere
ScopedValue.callWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
@ -348,23 +290,6 @@ class ScopedValueAPI {
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;
});
assertTrue(name.isBound());
assertEquals("duke", name.get());
return null;
});
assertFalse(name.isBound());
});
}
@ -377,7 +302,7 @@ class ScopedValueAPI {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
// run
// runWhere
ScopedValue.runWhere(name, null, () -> {
assertTrue(name.isBound());
assertNull(name.get());
@ -392,7 +317,7 @@ class ScopedValueAPI {
});
assertFalse(name.isBound());
// call
// callWhere
ScopedValue.callWhere(name, null, () -> {
assertTrue(name.isBound());
assertNull(name.get());
@ -408,23 +333,6 @@ class ScopedValueAPI {
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;
});
assertTrue(name.isBound());
assertNull(name.get());
return null;
});
assertFalse(name.isBound());
});
}
@ -437,7 +345,7 @@ class ScopedValueAPI {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
// run
// runWhere
ScopedValue.runWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
@ -452,7 +360,7 @@ class ScopedValueAPI {
});
assertFalse(name.isBound());
// call
// callWhere
ScopedValue.callWhere(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals("duke", name.get());
@ -468,23 +376,6 @@ class ScopedValueAPI {
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;
});
assertTrue(name.isBound());
assertEquals("duke", name.get());
return null;
});
assertFalse(name.isBound());
});
}
@ -517,16 +408,19 @@ class ScopedValueAPI {
void testNullPointerException() {
ScopedValue<String> name = ScopedValue.newInstance();
assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value"));
assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "value", () -> { }));
assertThrows(NullPointerException.class, () -> ScopedValue.getWhere(null, "value", () -> null));
assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "duke"));
assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "duke", () -> { }));
assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(name, "duke", null));
assertThrows(NullPointerException.class, () -> ScopedValue.callWhere(null, "duke", () -> ""));
assertThrows(NullPointerException.class, () -> ScopedValue.callWhere(name, "duke", null));
assertThrows(NullPointerException.class, () -> name.orElseThrow(null));
var carrier = ScopedValue.where(name, "duke");
assertThrows(NullPointerException.class, () -> carrier.where(null, "value"));
assertThrows(NullPointerException.class, () -> carrier.where(null, "duke"));
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));
}

View File

@ -47,8 +47,8 @@
* @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations StressStackOverflow
*/
import java.lang.ScopedValue.CallableOp;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.StructureViolationException;
import java.util.concurrent.StructuredTaskScope;
@ -68,12 +68,12 @@ public class StressStackOverflow {
static final long DURATION_IN_NANOS = Duration.ofMinutes(1).toNanos();
// Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable
// Test the ScopedValue recovery mechanism for stack overflows. We implement both CallableOp
// and Runnable interfaces. Which one gets tested depends on the constructor argument.
class DeepRecursion implements Callable<Object>, Supplier<Object>, Runnable {
class DeepRecursion implements CallableOp<Object, RuntimeException>, Supplier<Object>, Runnable {
enum Behaviour {
CALL, GET, RUN;
CALL, RUN;
private static final Behaviour[] values = values();
public static Behaviour choose(ThreadLocalRandom tlr) {
return values[tlr.nextInt(3)];
@ -97,7 +97,6 @@ public class StressStackOverflow {
try {
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));
}
if (!last.equals(el.get())) {

View File

@ -24,9 +24,8 @@
package org.openjdk.bench.java.lang;
import java.util.concurrent.Callable;
import java.lang.ScopedValue.CallableOp;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
@ -164,18 +163,9 @@ public class ScopedValues {
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Object bind_ScopedValue() throws Exception {
return HOLD_42.call(aCallable);
return HOLD_42.call(aCallableOp);
}
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;
private static final CallableOp<Class<?>, RuntimeException> aCallableOp = () -> ScopedValues.class;
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)