blob: 49e3ce3d7a04f79a43ad5ce71adc9e47c07afcbe [file] [log] [blame]
/*
* Copyright (c) 2016, 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.loader;
import jdk.internal.misc.JavaLangAccess;
import jdk.internal.misc.SharedSecrets;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue}
* and {@link Sub sub}-ClassLoaderValue.
*
* @param <CLV> the type of concrete ClassLoaderValue (this type)
* @param <V> the type of values associated with ClassLoaderValue
*/
public abstract class AbstractClassLoaderValue<CLV extends AbstractClassLoaderValue<CLV, V>, V> {
/**
* Sole constructor.
*/
AbstractClassLoaderValue() {}
/**
* Returns the key component of this ClassLoaderValue. The key component of
* the root-{@link ClassLoaderValue} is the ClassLoaderValue itself,
* while the key component of a {@link #sub(Object) sub}-ClassLoaderValue
* is what was given to construct it.
*
* @return the key component of this ClassLoaderValue.
*/
public abstract Object key();
/**
* Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given
* key component.
*
* @param key the key component of the sub-ClassLoaderValue.
* @param <K> the type of the key component.
* @return a sub-ClassLoaderValue of this ClassLoaderValue for given key
*/
public <K> Sub<K> sub(K key) {
return new Sub<>(key);
}
/**
* Returns {@code true} if this ClassLoaderValue is equal to given {@code clv}
* or if this ClassLoaderValue was derived from given {@code clv} by a chain
* of {@link #sub(Object)} invocations.
*
* @param clv the ClassLoaderValue to test this against
* @return if this ClassLoaderValue is equal to given {@code clv} or
* its descendant
*/
public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv);
/**
* Returns the value associated with this ClassLoaderValue and given ClassLoader
* or {@code null} if there is none.
*
* @param cl the ClassLoader for the associated value
* @return the value associated with this ClassLoaderValue and given ClassLoader
* or {@code null} if there is none.
*/
public V get(ClassLoader cl) {
Object val = AbstractClassLoaderValue.<CLV>map(cl).get(this);
try {
return extractValue(val);
} catch (Memoizer.RecursiveInvocationException e) {
// propagate recursive get() for the same key that is just
// being calculated in computeIfAbsent()
throw e;
} catch (Throwable t) {
// don't propagate exceptions thrown from Memoizer - pretend
// that there was no entry
// (computeIfAbsent invocation will try to remove it anyway)
return null;
}
}
/**
* Associates given value {@code v} with this ClassLoaderValue and given
* ClassLoader and returns {@code null} if there was no previously associated
* value or does nothing and returns previously associated value if there
* was one.
*
* @param cl the ClassLoader for the associated value
* @param v the value to associate
* @return previously associated value or null if there was none
*/
public V putIfAbsent(ClassLoader cl, V v) {
ConcurrentHashMap<CLV, Object> map = map(cl);
@SuppressWarnings("unchecked")
CLV clv = (CLV) this;
while (true) {
try {
Object val = map.putIfAbsent(clv, v);
return extractValue(val);
} catch (Memoizer.RecursiveInvocationException e) {
// propagate RecursiveInvocationException for the same key that
// is just being calculated in computeIfAbsent
throw e;
} catch (Throwable t) {
// don't propagate exceptions thrown from foreign Memoizer -
// pretend that there was no entry and retry
// (foreign computeIfAbsent invocation will try to remove it anyway)
}
// TODO:
// Thread.onSpinLoop(); // when available
}
}
/**
* Removes the value associated with this ClassLoaderValue and given
* ClassLoader if the associated value is equal to given value {@code v} and
* returns {@code true} or does nothing and returns {@code false} if there is
* no currently associated value or it is not equal to given value {@code v}.
*
* @param cl the ClassLoader for the associated value
* @param v the value to compare with currently associated value
* @return {@code true} if the association was removed or {@code false} if not
*/
public boolean remove(ClassLoader cl, Object v) {
return AbstractClassLoaderValue.<CLV>map(cl).remove(this, v);
}
/**
* Returns the value associated with this ClassLoaderValue and given
* ClassLoader if there is one or computes the value by invoking given
* {@code mappingFunction}, associates it and returns it.
* <p>
* Computation and association of the computed value is performed atomically
* by the 1st thread that requests a particular association while holding a
* lock associated with this ClassLoaderValue and given ClassLoader.
* Nested calls from the {@code mappingFunction} to {@link #get},
* {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association
* are not allowed and throw {@link IllegalStateException}. Nested call to
* {@link #remove} for the same association is allowed but will always return
* {@code false} regardless of passed-in comparison value. Nested calls for
* other association(s) are allowed, but care should be taken to avoid
* deadlocks. When two threads perform nested computations of the overlapping
* set of associations they should always request them in the same order.
*
* @param cl the ClassLoader for the associated value
* @param mappingFunction the function to compute the value
* @return the value associated with this ClassLoaderValue and given
* ClassLoader.
* @throws IllegalStateException if a direct or indirect invocation from
* within given {@code mappingFunction} that
* computes the value of a particular association
* to {@link #get}, {@link #putIfAbsent} or
* {@link #computeIfAbsent}
* for the same association is attempted.
*/
public V computeIfAbsent(ClassLoader cl,
BiFunction<
? super ClassLoader,
? super CLV,
? extends V
> mappingFunction) throws IllegalStateException {
ConcurrentHashMap<CLV, Object> map = map(cl);
@SuppressWarnings("unchecked")
CLV clv = (CLV) this;
Memoizer<CLV, V> mv = null;
while (true) {
Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv);
if (val == null) {
if (mv == null) {
// create Memoizer lazily when 1st needed and restart loop
mv = new Memoizer<>(cl, clv, mappingFunction);
continue;
}
// mv != null, therefore sv == null was a result of successful
// putIfAbsent
try {
// trigger Memoizer to compute the value
V v = mv.get();
// attempt to replace our Memoizer with the value
map.replace(clv, mv, v);
// return computed value
return v;
} catch (Throwable t) {
// our Memoizer has thrown, attempt to remove it
map.remove(clv, mv);
// propagate exception because it's from our Memoizer
throw t;
}
} else {
try {
return extractValue(val);
} catch (Memoizer.RecursiveInvocationException e) {
// propagate recursive attempts to calculate the same
// value as being calculated at the moment
throw e;
} catch (Throwable t) {
// don't propagate exceptions thrown from foreign Memoizer -
// pretend that there was no entry and retry
// (foreign computeIfAbsent invocation will try to remove it anyway)
}
}
// TODO:
// Thread.onSpinLoop(); // when available
}
}
/**
* Removes all values associated with given ClassLoader {@code cl} and
* {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants}
* of this ClassLoaderValue.
* This is not an atomic operation. Other threads may see some associations
* be already removed and others still present while this method is executing.
* <p>
* The sole intention of this method is to cleanup after a unit test that
* tests ClassLoaderValue directly. It is not intended for use in
* actual algorithms.
*
* @param cl the associated ClassLoader of the values to be removed
*/
public void removeAll(ClassLoader cl) {
ConcurrentHashMap<CLV, Object> map = map(cl);
for (Iterator<CLV> i = map.keySet().iterator(); i.hasNext(); ) {
if (i.next().isEqualOrDescendantOf(this)) {
i.remove();
}
}
}
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
/**
* @return a ConcurrentHashMap for given ClassLoader
*/
@SuppressWarnings("unchecked")
private static <CLV extends AbstractClassLoaderValue<CLV, ?>>
ConcurrentHashMap<CLV, Object> map(ClassLoader cl) {
return (ConcurrentHashMap<CLV, Object>)
(cl == null ? BootLoader.getClassLoaderValueMap()
: JLA.createOrGetClassLoaderValueMap(cl));
}
/**
* @return value extracted from the {@link Memoizer} if given
* {@code memoizerOrValue} parameter is a {@code Memoizer} or
* just return given parameter.
*/
@SuppressWarnings("unchecked")
private V extractValue(Object memoizerOrValue) {
if (memoizerOrValue instanceof Memoizer) {
return ((Memoizer<?, V>) memoizerOrValue).get();
} else {
return (V) memoizerOrValue;
}
}
/**
* A memoized supplier that invokes given {@code mappingFunction} just once
* and remembers the result or thrown exception for subsequent calls.
* If given mappingFunction returns null, it is converted to NullPointerException,
* thrown from the Memoizer's {@link #get()} method and remembered.
* If the Memoizer is invoked recursively from the given {@code mappingFunction},
* {@link RecursiveInvocationException} is thrown, but it is not remembered.
* The in-flight call to the {@link #get()} can still complete successfully if
* such exception is handled by the mappingFunction.
*/
private static final class Memoizer<CLV extends AbstractClassLoaderValue<CLV, V>, V>
implements Supplier<V> {
private final ClassLoader cl;
private final CLV clv;
private final BiFunction<? super ClassLoader, ? super CLV, ? extends V>
mappingFunction;
private volatile V v;
private volatile Throwable t;
private boolean inCall;
Memoizer(ClassLoader cl,
CLV clv,
BiFunction<? super ClassLoader, ? super CLV, ? extends V>
mappingFunction
) {
this.cl = cl;
this.clv = clv;
this.mappingFunction = mappingFunction;
}
@Override
public V get() throws RecursiveInvocationException {
V v = this.v;
if (v != null) return v;
Throwable t = this.t;
if (t == null) {
synchronized (this) {
if ((v = this.v) == null && (t = this.t) == null) {
if (inCall) {
throw new RecursiveInvocationException();
}
inCall = true;
try {
this.v = v = Objects.requireNonNull(
mappingFunction.apply(cl, clv));
} catch (Throwable x) {
this.t = t = x;
} finally {
inCall = false;
}
}
}
}
if (v != null) return v;
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new UndeclaredThrowableException(t);
}
}
static class RecursiveInvocationException extends IllegalStateException {
private static final long serialVersionUID = 1L;
RecursiveInvocationException() {
super("Recursive call");
}
}
}
/**
* sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue}
* and also a subclass of it. It can therefore be instantiated as an inner
* class of either an instance of root-{@link ClassLoaderValue} or another
* instance of itself. This enables composing type-safe compound keys of
* arbitrary length:
* <pre>{@code
* ClassLoaderValue<V> clv = new ClassLoaderValue<>();
* ClassLoaderValue<V>.Sub<K1>.Sub<K2>.Sub<K3> clv_k123 =
* clv.sub(k1).sub(k2).sub(k3);
* }</pre>
* From which individual components are accessible in a type-safe way:
* <pre>{@code
* K1 k1 = clv_k123.parent().parent().key();
* K2 k2 = clv_k123.parent().key();
* K3 k3 = clv_k123.key();
* }</pre>
* This allows specifying non-capturing lambdas for the mapping function of
* {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can
* access individual key components from passed-in
* sub-[sub-...]ClassLoaderValue instance in a type-safe way.
*
* @param <K> the type of {@link #key()} component contained in the
* sub-ClassLoaderValue.
*/
public final class Sub<K> extends AbstractClassLoaderValue<Sub<K>, V> {
private final K key;
Sub(K key) {
this.key = key;
}
/**
* @return the parent ClassLoaderValue this sub-ClassLoaderValue
* has been {@link #sub(Object) derived} from.
*/
public AbstractClassLoaderValue<CLV, V> parent() {
return AbstractClassLoaderValue.this;
}
/**
* @return the key component of this sub-ClassLoaderValue.
*/
@Override
public K key() {
return key;
}
/**
* sub-ClassLoaderValue is a descendant of given {@code clv} if it is
* either equal to it or if its {@link #parent() parent} is a
* descendant of given {@code clv}.
*/
@Override
public boolean isEqualOrDescendantOf(AbstractClassLoaderValue<?, V> clv) {
return equals(Objects.requireNonNull(clv)) ||
parent().isEqualOrDescendantOf(clv);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Sub)) return false;
@SuppressWarnings("unchecked")
Sub<?> that = (Sub<?>) o;
return this.parent().equals(that.parent()) &&
Objects.equals(this.key, that.key);
}
@Override
public int hashCode() {
return 31 * parent().hashCode() +
Objects.hashCode(key);
}
}
}