8331189: Implementation of Scoped Values (Third Preview)
Reviewed-by: aph, jpai, mcimadamore
This commit is contained in:
parent
4acafb809c
commit
707154235b
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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())) {
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user