diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 38cc45122a2e6..d3636a4637d7f 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -45,6 +45,7 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.UnderlyingHolder; import jdk.internal.misc.CDS; import jdk.internal.util.ArraysSupport; import jdk.internal.vm.annotation.ForceInline; @@ -137,11 +138,11 @@ public List listFromTrustedArrayNullsAllowed(Object[] array) { } public List stableList(int size, IntFunction mapper) { // A stable list is not Serializable, so we cannot return `List.of()` if `size == 0` - return new StableList<>(size, mapper); + return new StableList<>(size, new UnderlyingHolder<>(mapper, size)); } public Map stableMap(Set keys, Function mapper) { // A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()` - return new StableMap<>(keys, mapper); + return new StableMap<>(keys, new UnderlyingHolder<>(mapper, keys.size())); } }); } @@ -794,13 +795,13 @@ static final class StableList extends AbstractImmutableList implements HasStableDelegates { - @Stable - private final IntFunction mapper; @Stable final StableValueImpl[] delegates; + @Stable + private final UnderlyingHolder> underlyingHolder; - StableList(int size, IntFunction mapper) { - this.mapper = mapper; + StableList(int size, UnderlyingHolder> underlyingHolder) { + this.underlyingHolder = underlyingHolder; this.delegates = StableUtil.array(size); } @@ -818,7 +819,7 @@ public E get(int i) { throw new IndexOutOfBoundsException(i); } return delegate.orElseSet(new Supplier() { - @Override public E get() { return mapper.apply(i); }}); + @Override public E get() { return underlyingHolder.underlying().apply(i); }}, underlyingHolder); } @Override @@ -1606,14 +1607,14 @@ private Object writeReplace() { static final class StableMap extends AbstractImmutableMap { - @Stable - private final Function mapper; @Stable private final Map> delegate; + @Stable + private final UnderlyingHolder> underlyingHolder; - StableMap(Set keys, Function mapper) { - this.mapper = mapper; + StableMap(Set keys, UnderlyingHolder> underlyingHolder) { this.delegate = StableUtil.map(keys); + this.underlyingHolder = underlyingHolder; } @Override public boolean containsKey(Object o) { return delegate.containsKey(o); } @@ -1636,7 +1637,7 @@ public V getOrDefault(Object key, V defaultValue) { @SuppressWarnings("unchecked") final K k = (K) key; return stable.orElseSet(new Supplier() { - @Override public V get() { return mapper.apply(k); }}); + @Override public V get() { return underlyingHolder.underlying().apply(k); }}, underlyingHolder); } @jdk.internal.ValueBased @@ -1692,7 +1693,7 @@ public Entry next() { final Map.Entry> inner = delegateIterator.next(); final K k = inner.getKey(); return new StableEntry<>(k, inner.getValue(), new Supplier() { - @Override public V get() { return outer.outer.mapper.apply(k); }}); + @Override public V get() { return outer.outer.underlyingHolder.underlying().apply(k); }}, outer.outer.underlyingHolder); } @Override @@ -1703,7 +1704,7 @@ public void forEachRemaining(Consumer> action) { public void accept(Entry> inner) { final K k = inner.getKey(); action.accept(new StableEntry<>(k, inner.getValue(), new Supplier() { - @Override public V get() { return outer.outer.mapper.apply(k); }})); + @Override public V get() { return outer.outer.underlyingHolder.underlying().apply(k); }}, outer.outer.underlyingHolder)); } }; delegateIterator.forEachRemaining(innerAction); @@ -1719,10 +1720,11 @@ private static LazyMapIterator of(StableMapEntrySet outer) { private record StableEntry(K getKey, // trick StableValueImpl stableValue, - Supplier supplier) implements Map.Entry { + Supplier supplier, + UnderlyingHolder underlyingHolder) implements Map.Entry { @Override public V setValue(V value) { throw uoe(); } - @Override public V getValue() { return stableValue.orElseSet(supplier); } + @Override public V getValue() { return stableValue.orElseSet(supplier, underlyingHolder); } @Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); } @Override public String toString() { return getKey() + "=" + stableValue.toString(); } @Override public boolean equals(Object o) { diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java index d6893438be5de..2bd29284c6435 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java @@ -33,7 +33,6 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; -import java.util.EnumSet; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -46,20 +45,21 @@ * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * - * @param enumType the class type of the Enum - * @param firstOrdinal the lowest ordinal used - * @param member an int predicate that can be used to test if an enum is a member - * of the valid inputs (as there might be "holes") - * @param delegates a delegate array of inputs to StableValue mappings - * @param original the original Function - * @param the type of the input to the function - * @param the type of the result of the function + * @param enumType the class type of the Enum + * @param firstOrdinal the lowest ordinal used + * @param member an int predicate that can be used to test if an enum is a member + * of the valid inputs (as there might be "holes") + * @param delegates a delegate array of inputs to StableValue mappings + * @param underlyingHolder of the original underlying Function + * @param the type of the input to the function + * @param the type of the result of the function */ public record StableEnumFunction, R>(Class enumType, int firstOrdinal, IntPredicate member, @Stable StableValueImpl[] delegates, - Function original) implements Function { + UnderlyingHolder> underlyingHolder) implements Function { + @ForceInline @Override public R apply(E value) { @@ -69,7 +69,7 @@ public R apply(E value) { final int index = value.ordinal() - firstOrdinal; // Since we did the member.test above, we know the index is in bounds return delegates[index].orElseSet(new Supplier() { - @Override public R get() { return original.apply(value); }}); + @Override public R get() { return underlyingHolder.underlying().apply(value); }}, underlyingHolder); } @@ -96,9 +96,10 @@ public String toString() { return StableUtil.renderMappings(this, "StableFunction", entries, true); } + @SuppressWarnings("unchecked") public static , R> Function of(Set inputs, - Function original) { + Function underlying) { // The input set is not empty final Class enumType = ((E) inputs.iterator().next()).getDeclaringClass(); final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); @@ -112,7 +113,7 @@ public static , R> Function of(Set input } final int size = max - min + 1; final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); - return (Function) new StableEnumFunction(enumType, min, member, StableUtil.array(size), (Function) original); + return (Function) new StableEnumFunction(enumType, min, member, StableUtil.array(size), new UnderlyingHolder<>((Function) underlying, bitSet.cardinality())); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java index e36b4e9b25a0a..c84a2454f4cbc 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java @@ -35,19 +35,20 @@ // Note: It would be possible to just use `StableMap::get` with some additional logic // instead of this class but explicitly providing a class like this provides better // debug capability, exception handling, and may provide better performance. + /** * Implementation of a stable Function. * * @implNote This implementation can be used early in the boot sequence as it does not * rely on reflection, MethodHandles, Streams etc. * - * @param values a delegate map of inputs to StableValue mappings - * @param original the original Function - * @param the type of the input to the function - * @param the type of the result of the function + * @param values a delegate map of inputs to StableValue mappings + * @param underlyingHolder of the original underlying Function + * @param the type of the input to the function + * @param the type of the result of the function */ public record StableFunction(Map> values, - Function original) implements Function { + UnderlyingHolder> underlyingHolder) implements Function { @ForceInline @Override @@ -57,7 +58,7 @@ public R apply(T value) { throw new IllegalArgumentException("Input not allowed: " + value); } return stable.orElseSet(new Supplier() { - @Override public R get() { return original.apply(value); }}); + @Override public R get() { return underlyingHolder.underlying().apply(value); }}, underlyingHolder); } @Override @@ -76,8 +77,8 @@ public String toString() { } public static StableFunction of(Set inputs, - Function original) { - return new StableFunction<>(StableUtil.map(inputs), original); + Function underlying) { + return new StableFunction<>(StableUtil.map(inputs), new UnderlyingHolder<>(underlying, inputs.size())); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java index a921a4de87b2d..279d491f89861 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java @@ -43,19 +43,19 @@ * @param the return type */ public record StableIntFunction(@Stable StableValueImpl[] delegates, - IntFunction original) implements IntFunction { + UnderlyingHolder> underlyingHolder) implements IntFunction { @ForceInline @Override public R apply(int index) { final StableValueImpl delegate; try { - delegate = delegates[index]; + delegate = delegates[index]; } catch (ArrayIndexOutOfBoundsException ioob) { throw new IllegalArgumentException("Input not allowed: " + index, ioob); } return delegate.orElseSet(new Supplier() { - @Override public R get() { return original.apply(index); }}); + @Override public R get() { return underlyingHolder.underlying().apply(index); }}, underlyingHolder); } @Override @@ -73,8 +73,8 @@ public String toString() { return StableUtil.renderElements(this, "StableIntFunction", delegates); } - public static StableIntFunction of(int size, IntFunction original) { - return new StableIntFunction<>(StableUtil.array(size), original); + public static StableIntFunction of(int size, IntFunction underlying) { + return new StableIntFunction<>(StableUtil.array(size), new UnderlyingHolder<>(underlying, size)); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java index 631a41c5710ef..109f46ba570e1 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java @@ -38,12 +38,12 @@ * @param the return type */ public record StableSupplier(StableValueImpl delegate, - Supplier original) implements Supplier { + UnderlyingHolder> underlyingHolder) implements Supplier { @ForceInline @Override public T get() { - return delegate.orElseSet(original); + return delegate.orElseSet(underlyingHolder.underlying(), underlyingHolder); } @Override @@ -62,8 +62,8 @@ public String toString() { return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t); } - public static StableSupplier of(Supplier original) { - return new StableSupplier<>(StableValueImpl.of(), original); + public static StableSupplier of(Supplier underlying) { + return new StableSupplier<>(StableValueImpl.of(), new UnderlyingHolder<>(underlying, 1)); } } diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java index 1413fd7446e4f..f837cd8f779b9 100644 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java +++ b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java @@ -126,12 +126,20 @@ public boolean isSet() { @Override public T orElseSet(Supplier supplier) { Objects.requireNonNull(supplier); + return orElseSet(supplier, null); + } + + // `supplier` can be null if the `underlyingHolder` released it. + @ForceInline + public T orElseSet(Supplier supplier, + UnderlyingHolder underlyingHolder) { final Object t = wrappedContentsAcquire(); - return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); + return (t == null) ? orElseSetSlowPath(supplier, underlyingHolder) : unwrap(t); } @DontInline - private T orElseSetSlowPath(Supplier supplier) { + private T orElseSetSlowPath(Supplier supplier, + UnderlyingHolder underlyingHolder) { preventReentry(); synchronized (this) { final Object t = contents; // Plain semantics suffice here @@ -139,6 +147,11 @@ private T orElseSetSlowPath(Supplier supplier) { final T newValue = supplier.get(); // The mutex is not reentrant so we know newValue should be returned wrapAndSet(newValue); + if (underlyingHolder != null) { + // Reduce the counter and if it reaches zero, clear the reference + // to the underlying holder. + underlyingHolder.countDown(); + } return newValue; } return unwrap(t); diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/UnderlyingHolder.java b/src/java.base/share/classes/jdk/internal/lang/stable/UnderlyingHolder.java new file mode 100644 index 0000000000000..59b6fdff80134 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/stable/UnderlyingHolder.java @@ -0,0 +1,55 @@ +package jdk.internal.lang.stable; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.ForceInline; + +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.joining; + +/** + * This class is thread safe. + * + * @param the underlying type + */ +public final class UnderlyingHolder { + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final long COUNTER_OFFSET = + UNSAFE.objectFieldOffset(UnderlyingHolder.class, "counter"); + + // Used reflectively. This field can only transition at most once from being set to a + // non-null reference to being `null`. Once `null`, it is never read. This allows + // the field to be non-volatile, which is crucial for getting optimum performance. + private U underlying; + + // Used reflectively + private int counter; + + public UnderlyingHolder(U underlying, int counter) { + this.underlying = underlying; + this.counter = counter; + // Safe publication + UNSAFE.storeStoreFence(); + } + + @ForceInline + public U underlying() { + return underlying; + } + + // For testing only + public int counter() { + return UNSAFE.getIntVolatile(this, COUNTER_OFFSET); + } + + public void countDown() { + if (UNSAFE.getAndAddInt(this, COUNTER_OFFSET, -1) == 1) { + // Do not reference the underlying function anymore so it can be collected. + underlying = null; + } + } + +} diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java index 07f51470a0b3d..8a064c42c3475 100644 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableFunctionTest.java @@ -24,9 +24,11 @@ /* @test * @summary Basic tests for StableFunction methods * @enablePreview - * @run junit StableFunctionTest + * @modules java.base/jdk.internal.lang.stable + * @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED StableFunctionTest */ +import jdk.internal.lang.stable.UnderlyingHolder; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -209,6 +211,26 @@ void overriddenEnum() { assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden)); } + @ParameterizedTest + @MethodSource("nonEmptySets") + void underlyingRef(Set inputs) { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(MAPPER); + Function f1 = StableValue.function(inputs, cif); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + + int i = 0; + for (Value input : inputs) { + assertEquals(inputs.size() - i, holder.counter()); + assertSame(cif, holder.underlying()); + int v = f1.apply(input); + int v2 = f1.apply(input); + i++; + } + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + private static Stream> nonEmptySets() { return Stream.of( Set.of(Value.FORTY_TWO, Value.THIRTEEN), diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java index 7397a688ee68f..577e1611daf7f 100644 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java @@ -23,10 +23,12 @@ /* @test * @summary Basic tests for StableIntFunction methods + * @modules java.base/jdk.internal.lang.stable * @enablePreview - * @run junit StableIntFunctionTest + * @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED StableIntFunctionTest */ +import jdk.internal.lang.stable.UnderlyingHolder; import org.junit.jupiter.api.Test; import java.util.concurrent.atomic.AtomicReference; @@ -106,4 +108,20 @@ void hashCodeStable() { assertEquals(System.identityHashCode(f0), f0.hashCode()); } + @Test + void underlyingRef() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(MAPPER); + IntFunction f1 = StableValue.intFunction(SIZE, cif); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + for (int i = 0; i < SIZE; i++) { + assertEquals(SIZE - i, holder.counter()); + assertSame(cif, holder.underlying()); + int v = f1.apply(i); + int v2 = f1.apply(i); + } + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + } diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/StableValue/StableListTest.java index 2abe305b0e7ae..5de5eac8a0afe 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/StableValue/StableListTest.java @@ -25,11 +25,12 @@ * @summary Basic tests for StableList methods * @modules java.base/jdk.internal.lang.stable * @enablePreview - * @run junit StableListTest + * @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED StableListTest */ import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.UnderlyingHolder; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -433,6 +434,22 @@ void childObjectOpsLazy() { }); } + @Test + void underlyingRef() { + StableTestUtil.CountingIntFunction cif = new StableTestUtil.CountingIntFunction<>(IDENTITY); + List f1 = StableValue.list(SIZE, cif); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + for (int i = 0; i < SIZE; i++) { + assertEquals(SIZE - i, holder.counter()); + assertSame(cif, holder.underlying()); + int v = f1.get(i); + int v2 = f1.get(i); + } + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + // Support constructs record Operation(String name, diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java index 86cf4ab3643ad..9d632aeb2159b 100644 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ b/test/jdk/java/lang/StableValue/StableMapTest.java @@ -25,11 +25,12 @@ * @summary Basic tests for StableMap methods * @modules java.base/jdk.internal.lang.stable * @enablePreview - * @run junit StableMapTest + * @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED StableMapTest */ import jdk.internal.lang.stable.StableUtil; import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.UnderlyingHolder; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -40,6 +41,7 @@ import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -340,6 +342,63 @@ void nullKeys() { assertThrows(NullPointerException.class, () -> StableValue.map(inputs, IDENTITY)); } + @Test + void underlyingRef() { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(IDENTITY); + Map f1 = StableValue.map(KEYS, cif); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + + int i = 0; + for (Integer input : KEYS) { + assertEquals(KEYS.size() - i, holder.counter()); + assertSame(cif, holder.underlying()); + int v = f1.get(input); + int v2 = f1.get(input); + i++; + } + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + + @Test + void underlyingRefViaEntrySet() { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(IDENTITY); + Map f1 = StableValue.map(KEYS, cif); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + + int i = 0; + for (Map.Entry e : f1.entrySet()) { + assertEquals(KEYS.size() - i, holder.counter()); + assertSame(cif, holder.underlying()); + int v = e.getValue(); + int v2 = e.getValue(); + i++; + } + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + + @Test + void underlyingRefViaEntrySetForEach() { + StableTestUtil.CountingFunction cif = new StableTestUtil.CountingFunction<>(IDENTITY); + Map f1 = StableValue.map(KEYS, cif); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + + final AtomicInteger i = new AtomicInteger(); + f1.entrySet().forEach(e -> { + assertEquals(KEYS.size() - i.get(), holder.counter()); + assertSame(cif, holder.underlying()); + Integer val = e.getValue(); + Integer val2 = e.getValue(); + i.incrementAndGet(); + }); + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + // Support constructs record Operation(String name, diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java index 2d542fbf6cacf..b711c6cbc8dc6 100644 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ b/test/jdk/java/lang/StableValue/StableSupplierTest.java @@ -24,13 +24,16 @@ /* @test * @summary Basic tests for StableSupplier methods * @enablePreview - * @run junit StableSupplierTest + * @modules java.base/jdk.internal.lang.stable + * @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED StableSupplierTest */ +import jdk.internal.lang.stable.UnderlyingHolder; import org.junit.jupiter.api.Test; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.IntFunction; import java.util.function.Supplier; import static org.junit.jupiter.api.Assertions.*; @@ -101,4 +104,37 @@ void hashCodeStable() { assertEquals(System.identityHashCode(f0), f0.hashCode()); } + @Test + void underlyingRef() { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(SUPPLIER); + var f1 = StableValue.supplier(cs); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + assertEquals(1, holder.counter()); + assertSame(cs, holder.underlying()); + int v = f1.get(); + int v2 = f1.get(); + assertEquals(0, holder.counter()); + assertNull(holder.underlying()); + } + + @Test + void underlyingRefException() { + StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> { + throw new UnsupportedOperationException(); + }); + var f1 = StableValue.supplier(cs); + + UnderlyingHolder holder = StableTestUtil.underlyingHolder(f1); + assertEquals(1, holder.counter()); + assertSame(cs, holder.underlying()); + try { + int v = f1.get(); + } catch (UnsupportedOperationException _) { + // Expected + } + assertEquals(1, holder.counter()); + assertSame(cs, holder.underlying()); + } + } diff --git a/test/jdk/java/lang/StableValue/StableTestUtil.java b/test/jdk/java/lang/StableValue/StableTestUtil.java index f71915c28ee7e..60511c8efd2db 100644 --- a/test/jdk/java/lang/StableValue/StableTestUtil.java +++ b/test/jdk/java/lang/StableValue/StableTestUtil.java @@ -21,6 +21,9 @@ * questions. */ +import jdk.internal.lang.stable.UnderlyingHolder; + +import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Function; @@ -117,4 +120,14 @@ public final String toString() { } } + static UnderlyingHolder underlyingHolder(Object o) { + try { + final Field field = o.getClass().getDeclaredField("underlyingHolder"); + field.setAccessible(true); + return (UnderlyingHolder) field.get(o); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java index 4290c8716a046..24c0003351efa 100644 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ b/test/jdk/java/lang/StableValue/StableValueTest.java @@ -24,12 +24,12 @@ /* @test * @summary Basic tests for StableValue implementations * @enablePreview + * @modules java.base/jdk.internal.lang.stable * @run junit StableValueTest */ import org.junit.jupiter.api.Test; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -37,9 +37,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.IntFunction;