blob: 1413fd7446e4feb358a162b76a7401a43347eb11 [file]
/*
* Copyright (c) 2025, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.lang.stable;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.DontInline;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Supplier;
/**
* The implementation of StableValue.
*
* @implNote This implementation can be used early in the boot sequence as it does not
* rely on reflection, MethodHandles, Streams etc.
*
* @param <T> type of the contents
*/
public final class StableValueImpl<T> implements StableValue<T> {
static final String UNSET_LABEL = ".unset";
// Unsafe allows StableValue to be used early in the boot sequence
static final Unsafe UNSAFE = Unsafe.getUnsafe();
// Unsafe offsets for direct field access
private static final long CONTENTS_OFFSET =
UNSAFE.objectFieldOffset(StableValueImpl.class, "contents");
// Used to indicate a holder value is `null` (see field `contents` below)
private static final Object NULL_SENTINEL = new Object();
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
// memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`).
//
// This field is used directly and reflectively via Unsafe using explicit memory semantics.
//
// | Value | Meaning |
// | -------------- | ------------ |
// | null | Unset |
// | NULL_SENTINEL | Set(null) |
// | other | Set(other) |
//
@Stable
private Object contents;
// Only allow creation via the factory `StableValueImpl::newInstance`
private StableValueImpl() {}
@ForceInline
@Override
public boolean trySet(T contents) {
if (wrappedContentsAcquire() != null) {
return false;
}
// Prevent reentry via an orElseSet(supplier)
preventReentry();
// Mutual exclusion is required here as `orElseSet` might also
// attempt to modify `this.contents`
synchronized (this) {
return wrapAndSet(contents);
}
}
@ForceInline
@Override
public void setOrThrow(T contents) {
if (!trySet(contents)) {
// Neither the set contents nor the provided contents is revealed in the
// exception message as it might be sensitive.
throw new IllegalStateException("The contents is already set");
}
}
@ForceInline
@Override
public T orElseThrow() {
final Object t = wrappedContentsAcquire();
if (t == null) {
throw new NoSuchElementException("No contents set");
}
return unwrap(t);
}
@ForceInline
@Override
public T orElse(T other) {
final Object t = wrappedContentsAcquire();
return (t == null) ? other : unwrap(t);
}
@ForceInline
@Override
public boolean isSet() {
return wrappedContentsAcquire() != null;
}
@ForceInline
@Override
public T orElseSet(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier);
final Object t = wrappedContentsAcquire();
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
}
@DontInline
private T orElseSetSlowPath(Supplier<? extends T> supplier) {
preventReentry();
synchronized (this) {
final Object t = contents; // Plain semantics suffice here
if (t == null) {
final T newValue = supplier.get();
// The mutex is not reentrant so we know newValue should be returned
wrapAndSet(newValue);
return newValue;
}
return unwrap(t);
}
}
// The methods equals() and hashCode() should be based on identity (defaults from Object)
@Override
public String toString() {
final Object t = wrappedContentsAcquire();
return t == this
? "(this StableValue)"
: renderWrapped(t);
}
// Internal methods shared with other internal classes
@ForceInline
public Object wrappedContentsAcquire() {
return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET);
}
static String renderWrapped(Object t) {
return (t == null) ? UNSET_LABEL : Objects.toString(unwrap(t));
}
// Private methods
// This method is not annotated with @ForceInline as it is always called
// in a slow path.
private void preventReentry() {
if (Thread.holdsLock(this)) {
throw new IllegalStateException("Recursive initialization of a stable value is illegal");
}
}
/**
* Wraps the provided {@code newValue} and tries to set the contents.
* <p>
* This method ensures the {@link Stable} field is written to at most once.
*
* @param newValue to wrap and set
* @return if the contents was set
*/
@ForceInline
private boolean wrapAndSet(T newValue) {
assert Thread.holdsLock(this);
// We know we hold the monitor here so plain semantic is enough
if (contents == null) {
UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue));
return true;
}
return false;
}
// Wraps `null` values into a sentinel value
@ForceInline
private static Object wrap(Object t) {
return (t == null) ? NULL_SENTINEL : t;
}
// Unwraps null sentinel values into `null`
@SuppressWarnings("unchecked")
@ForceInline
private static <T> T unwrap(Object t) {
return t != NULL_SENTINEL ? (T) t : null;
}
// Factory
public static <T> StableValueImpl<T> of() {
return new StableValueImpl<>();
}
}