jaroslav@1649: /* jaroslav@1649: * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. jaroslav@1649: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jaroslav@1649: * jaroslav@1649: * This code is free software; you can redistribute it and/or modify it jaroslav@1649: * under the terms of the GNU General Public License version 2 only, as jaroslav@1649: * published by the Free Software Foundation. Oracle designates this jaroslav@1649: * particular file as subject to the "Classpath" exception as provided jaroslav@1649: * by Oracle in the LICENSE file that accompanied this code. jaroslav@1649: * jaroslav@1649: * This code is distributed in the hope that it will be useful, but WITHOUT jaroslav@1649: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jaroslav@1649: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jaroslav@1649: * version 2 for more details (a copy is included in the LICENSE file that jaroslav@1649: * accompanied this code). jaroslav@1649: * jaroslav@1649: * You should have received a copy of the GNU General Public License version jaroslav@1649: * 2 along with this work; if not, write to the Free Software Foundation, jaroslav@1649: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jaroslav@1649: * jaroslav@1649: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA jaroslav@1649: * or visit www.oracle.com if you need additional information or have any jaroslav@1649: * questions. jaroslav@1649: */ jaroslav@1649: jaroslav@1649: package java.lang; jaroslav@1649: jaroslav@1649: import java.lang.ClassValue.ClassValueMap; jaroslav@1649: import java.util.WeakHashMap; jaroslav@1649: import java.lang.ref.WeakReference; jaroslav@1649: import java.util.concurrent.atomic.AtomicInteger; jaroslav@1649: jaroslav@1649: import static java.lang.ClassValue.ClassValueMap.probeHomeLocation; jaroslav@1649: import static java.lang.ClassValue.ClassValueMap.probeBackupLocations; jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Lazily associate a computed value with (potentially) every type. jaroslav@1649: * For example, if a dynamic language needs to construct a message dispatch jaroslav@1649: * table for each class encountered at a message send call site, jaroslav@1649: * it can use a {@code ClassValue} to cache information needed to jaroslav@1649: * perform the message send quickly, for each class encountered. jaroslav@1649: * @author John Rose, JSR 292 EG jaroslav@1649: * @since 1.7 jaroslav@1649: */ jaroslav@1649: public abstract class ClassValue { jaroslav@1649: /** jaroslav@1649: * Sole constructor. (For invocation by subclass constructors, typically jaroslav@1649: * implicit.) jaroslav@1649: */ jaroslav@1649: protected ClassValue() { jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Computes the given class's derived value for this {@code ClassValue}. jaroslav@1649: *

jaroslav@1649: * This method will be invoked within the first thread that accesses jaroslav@1649: * the value with the {@link #get get} method. jaroslav@1649: *

jaroslav@1649: * Normally, this method is invoked at most once per class, jaroslav@1649: * but it may be invoked again if there has been a call to jaroslav@1649: * {@link #remove remove}. jaroslav@1649: *

jaroslav@1649: * If this method throws an exception, the corresponding call to {@code get} jaroslav@1649: * will terminate abnormally with that exception, and no class value will be recorded. jaroslav@1649: * jaroslav@1649: * @param type the type whose class value must be computed jaroslav@1649: * @return the newly computed value associated with this {@code ClassValue}, for the given class or interface jaroslav@1649: * @see #get jaroslav@1649: * @see #remove jaroslav@1649: */ jaroslav@1649: protected abstract T computeValue(Class type); jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Returns the value for the given class. jaroslav@1649: * If no value has yet been computed, it is obtained by jaroslav@1649: * an invocation of the {@link #computeValue computeValue} method. jaroslav@1649: *

jaroslav@1649: * The actual installation of the value on the class jaroslav@1649: * is performed atomically. jaroslav@1649: * At that point, if several racing threads have jaroslav@1649: * computed values, one is chosen, and returned to jaroslav@1649: * all the racing threads. jaroslav@1649: *

jaroslav@1649: * The {@code type} parameter is typically a class, but it may be any type, jaroslav@1649: * such as an interface, a primitive type (like {@code int.class}), or {@code void.class}. jaroslav@1649: *

jaroslav@1649: * In the absence of {@code remove} calls, a class value has a simple jaroslav@1649: * state diagram: uninitialized and initialized. jaroslav@1649: * When {@code remove} calls are made, jaroslav@1649: * the rules for value observation are more complex. jaroslav@1649: * See the documentation for {@link #remove remove} for more information. jaroslav@1649: * jaroslav@1649: * @param type the type whose class value must be computed or retrieved jaroslav@1649: * @return the current value associated with this {@code ClassValue}, for the given class or interface jaroslav@1649: * @throws NullPointerException if the argument is null jaroslav@1649: * @see #remove jaroslav@1649: * @see #computeValue jaroslav@1649: */ jaroslav@1649: public T get(Class type) { jaroslav@1649: // non-racing this.hashCodeForCache : final int jaroslav@1649: Entry[] cache; jaroslav@1649: Entry e = probeHomeLocation(cache = getCacheCarefully(type), this); jaroslav@1649: // racing e : current value <=> stale value from current cache or from stale cache jaroslav@1649: // invariant: e is null or an Entry with readable Entry.version and Entry.value jaroslav@1649: if (match(e)) jaroslav@1649: // invariant: No false positive matches. False negatives are OK if rare. jaroslav@1649: // The key fact that makes this work: if this.version == e.version, jaroslav@1649: // then this thread has a right to observe (final) e.value. jaroslav@1649: return e.value(); jaroslav@1649: // The fast path can fail for any of these reasons: jaroslav@1649: // 1. no entry has been computed yet jaroslav@1649: // 2. hash code collision (before or after reduction mod cache.length) jaroslav@1649: // 3. an entry has been removed (either on this type or another) jaroslav@1649: // 4. the GC has somehow managed to delete e.version and clear the reference jaroslav@1649: return getFromBackup(cache, type); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Removes the associated value for the given class. jaroslav@1649: * If this value is subsequently {@linkplain #get read} for the same class, jaroslav@1649: * its value will be reinitialized by invoking its {@link #computeValue computeValue} method. jaroslav@1649: * This may result in an additional invocation of the jaroslav@1649: * {@code computeValue} method for the given class. jaroslav@1649: *

jaroslav@1649: * In order to explain the interaction between {@code get} and {@code remove} calls, jaroslav@1649: * we must model the state transitions of a class value to take into account jaroslav@1649: * the alternation between uninitialized and initialized states. jaroslav@1649: * To do this, number these states sequentially from zero, and note that jaroslav@1649: * uninitialized (or removed) states are numbered with even numbers, jaroslav@1649: * while initialized (or re-initialized) states have odd numbers. jaroslav@1649: *

jaroslav@1649: * When a thread {@code T} removes a class value in state {@code 2N}, jaroslav@1649: * nothing happens, since the class value is already uninitialized. jaroslav@1649: * Otherwise, the state is advanced atomically to {@code 2N+1}. jaroslav@1649: *

jaroslav@1649: * When a thread {@code T} queries a class value in state {@code 2N}, jaroslav@1649: * the thread first attempts to initialize the class value to state {@code 2N+1} jaroslav@1649: * by invoking {@code computeValue} and installing the resulting value. jaroslav@1649: *

jaroslav@1649: * When {@code T} attempts to install the newly computed value, jaroslav@1649: * if the state is still at {@code 2N}, the class value will be initialized jaroslav@1649: * with the computed value, advancing it to state {@code 2N+1}. jaroslav@1649: *

jaroslav@1649: * Otherwise, whether the new state is even or odd, jaroslav@1649: * {@code T} will discard the newly computed value jaroslav@1649: * and retry the {@code get} operation. jaroslav@1649: *

jaroslav@1649: * Discarding and retrying is an important proviso, jaroslav@1649: * since otherwise {@code T} could potentially install jaroslav@1649: * a disastrously stale value. For example: jaroslav@1649: *

jaroslav@1649: * We can assume in the above scenario that {@code CV.computeValue} uses locks to properly jaroslav@1649: * observe the time-dependent states as it computes {@code V1}, etc. jaroslav@1649: * This does not remove the threat of a stale value, since there is a window of time jaroslav@1649: * between the return of {@code computeValue} in {@code T} and the installation jaroslav@1649: * of the the new value. No user synchronization is possible during this time. jaroslav@1649: * jaroslav@1649: * @param type the type whose class value must be removed jaroslav@1649: * @throws NullPointerException if the argument is null jaroslav@1649: */ jaroslav@1649: public void remove(Class type) { jaroslav@1649: ClassValueMap map = getMap(type); jaroslav@1649: map.removeEntry(this); jaroslav@1649: } jaroslav@1649: jaroslav@1649: // Possible functionality for JSR 292 MR 1 jaroslav@1649: /*public*/ void put(Class type, T value) { jaroslav@1649: ClassValueMap map = getMap(type); jaroslav@1649: map.changeEntry(this, value); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /// -------- jaroslav@1649: /// Implementation... jaroslav@1649: /// -------- jaroslav@1649: jaroslav@1649: /** Return the cache, if it exists, else a dummy empty cache. */ jaroslav@1649: private static Entry[] getCacheCarefully(Class type) { jaroslav@1649: // racing type.classValueMap{.cacheArray} : null => new Entry[X] <=> new Entry[Y] jaroslav@1649: ClassValueMap map = type.classValueMap; jaroslav@1649: if (map == null) return EMPTY_CACHE; jaroslav@1649: Entry[] cache = map.getCache(); jaroslav@1649: return cache; jaroslav@1649: // invariant: returned value is safe to dereference and check for an Entry jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Initial, one-element, empty cache used by all Class instances. Must never be filled. */ jaroslav@1649: private static final Entry[] EMPTY_CACHE = { null }; jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Slow tail of ClassValue.get to retry at nearby locations in the cache, jaroslav@1649: * or take a slow lock and check the hash table. jaroslav@1649: * Called only if the first probe was empty or a collision. jaroslav@1649: * This is a separate method, so compilers can process it independently. jaroslav@1649: */ jaroslav@1649: private T getFromBackup(Entry[] cache, Class type) { jaroslav@1649: Entry e = probeBackupLocations(cache, this); jaroslav@1649: if (e != null) jaroslav@1649: return e.value(); jaroslav@1649: return getFromHashMap(type); jaroslav@1649: } jaroslav@1649: jaroslav@1649: // Hack to suppress warnings on the (T) cast, which is a no-op. jaroslav@1649: @SuppressWarnings("unchecked") jaroslav@1649: Entry castEntry(Entry e) { return (Entry) e; } jaroslav@1649: jaroslav@1649: /** Called when the fast path of get fails, and cache reprobe also fails. jaroslav@1649: */ jaroslav@1649: private T getFromHashMap(Class type) { jaroslav@1649: // The fail-safe recovery is to fall back to the underlying classValueMap. jaroslav@1649: ClassValueMap map = getMap(type); jaroslav@1649: for (;;) { jaroslav@1649: Entry e = map.startEntry(this); jaroslav@1649: if (!e.isPromise()) jaroslav@1649: return e.value(); jaroslav@1649: try { jaroslav@1649: // Try to make a real entry for the promised version. jaroslav@1649: e = makeEntry(e.version(), computeValue(type)); jaroslav@1649: } finally { jaroslav@1649: // Whether computeValue throws or returns normally, jaroslav@1649: // be sure to remove the empty entry. jaroslav@1649: e = map.finishEntry(this, e); jaroslav@1649: } jaroslav@1649: if (e != null) jaroslav@1649: return e.value(); jaroslav@1649: // else try again, in case a racing thread called remove (so e == null) jaroslav@1649: } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Check that e is non-null, matches this ClassValue, and is live. */ jaroslav@1649: boolean match(Entry e) { jaroslav@1649: // racing e.version : null (blank) => unique Version token => null (GC-ed version) jaroslav@1649: // non-racing this.version : v1 => v2 => ... (updates are read faithfully from volatile) jaroslav@1649: return (e != null && e.get() == this.version); jaroslav@1649: // invariant: No false positives on version match. Null is OK for false negative. jaroslav@1649: // invariant: If version matches, then e.value is readable (final set in Entry.) jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Internal hash code for accessing Class.classValueMap.cacheArray. */ jaroslav@1649: final int hashCodeForCache = nextHashCode.getAndAdd(HASH_INCREMENT) & HASH_MASK; jaroslav@1649: jaroslav@1649: /** Value stream for hashCodeForCache. See similar structure in ThreadLocal. */ jaroslav@1649: private static final AtomicInteger nextHashCode = new AtomicInteger(); jaroslav@1649: jaroslav@1649: /** Good for power-of-two tables. See similar structure in ThreadLocal. */ jaroslav@1649: private static final int HASH_INCREMENT = 0x61c88647; jaroslav@1649: jaroslav@1649: /** Mask a hash code to be positive but not too large, to prevent wraparound. */ jaroslav@1649: static final int HASH_MASK = (-1 >>> 2); jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Private key for retrieval of this object from ClassValueMap. jaroslav@1649: */ jaroslav@1649: static class Identity { jaroslav@1649: } jaroslav@1649: /** jaroslav@1649: * This ClassValue's identity, expressed as an opaque object. jaroslav@1649: * The main object {@code ClassValue.this} is incorrect since jaroslav@1649: * subclasses may override {@code ClassValue.equals}, which jaroslav@1649: * could confuse keys in the ClassValueMap. jaroslav@1649: */ jaroslav@1649: final Identity identity = new Identity(); jaroslav@1649: jaroslav@1649: /** jaroslav@1649: * Current version for retrieving this class value from the cache. jaroslav@1649: * Any number of computeValue calls can be cached in association with one version. jaroslav@1649: * But the version changes when a remove (on any type) is executed. jaroslav@1649: * A version change invalidates all cache entries for the affected ClassValue, jaroslav@1649: * by marking them as stale. Stale cache entries do not force another call jaroslav@1649: * to computeValue, but they do require a synchronized visit to a backing map. jaroslav@1649: *

jaroslav@1649: * All user-visible state changes on the ClassValue take place under jaroslav@1649: * a lock inside the synchronized methods of ClassValueMap. jaroslav@1649: * Readers (of ClassValue.get) are notified of such state changes jaroslav@1649: * when this.version is bumped to a new token. jaroslav@1649: * This variable must be volatile so that an unsynchronized reader jaroslav@1649: * will receive the notification without delay. jaroslav@1649: *

jaroslav@1649: * If version were not volatile, one thread T1 could persistently hold onto jaroslav@1649: * a stale value this.value == V1, while while another thread T2 advances jaroslav@1649: * (under a lock) to this.value == V2. This will typically be harmless, jaroslav@1649: * but if T1 and T2 interact causally via some other channel, such that jaroslav@1649: * T1's further actions are constrained (in the JMM) to happen after jaroslav@1649: * the V2 event, then T1's observation of V1 will be an error. jaroslav@1649: *

jaroslav@1649: * The practical effect of making this.version be volatile is that it cannot jaroslav@1649: * be hoisted out of a loop (by an optimizing JIT) or otherwise cached. jaroslav@1649: * Some machines may also require a barrier instruction to execute jaroslav@1649: * before this.version. jaroslav@1649: */ jaroslav@1649: private volatile Version version = new Version<>(this); jaroslav@1649: Version version() { return version; } jaroslav@1649: void bumpVersion() { version = new Version<>(this); } jaroslav@1649: static class Version { jaroslav@1649: private final ClassValue classValue; jaroslav@1649: private final Entry promise = new Entry<>(this); jaroslav@1649: Version(ClassValue classValue) { this.classValue = classValue; } jaroslav@1649: ClassValue classValue() { return classValue; } jaroslav@1649: Entry promise() { return promise; } jaroslav@1649: boolean isLive() { return classValue.version() == this; } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** One binding of a value to a class via a ClassValue. jaroslav@1649: * States are:

    jaroslav@1649: *
  • promise if value == Entry.this jaroslav@1649: *
  • else dead if version == null jaroslav@1649: *
  • else stale if version != classValue.version jaroslav@1649: *
  • else live
jaroslav@1649: * Promises are never put into the cache; they only live in the jaroslav@1649: * backing map while a computeValue call is in flight. jaroslav@1649: * Once an entry goes stale, it can be reset at any time jaroslav@1649: * into the dead state. jaroslav@1649: */ jaroslav@1649: static class Entry extends WeakReference> { jaroslav@1649: final Object value; // usually of type T, but sometimes (Entry)this jaroslav@1649: Entry(Version version, T value) { jaroslav@1649: super(version); jaroslav@1649: this.value = value; // for a regular entry, value is of type T jaroslav@1649: } jaroslav@1649: private void assertNotPromise() { assert(!isPromise()); } jaroslav@1649: /** For creating a promise. */ jaroslav@1649: Entry(Version version) { jaroslav@1649: super(version); jaroslav@1649: this.value = this; // for a promise, value is not of type T, but Entry! jaroslav@1649: } jaroslav@1649: /** Fetch the value. This entry must not be a promise. */ jaroslav@1649: @SuppressWarnings("unchecked") // if !isPromise, type is T jaroslav@1649: T value() { assertNotPromise(); return (T) value; } jaroslav@1649: boolean isPromise() { return value == this; } jaroslav@1649: Version version() { return get(); } jaroslav@1649: ClassValue classValueOrNull() { jaroslav@1649: Version v = version(); jaroslav@1649: return (v == null) ? null : v.classValue(); jaroslav@1649: } jaroslav@1649: boolean isLive() { jaroslav@1649: Version v = version(); jaroslav@1649: if (v == null) return false; jaroslav@1649: if (v.isLive()) return true; jaroslav@1649: clear(); jaroslav@1649: return false; jaroslav@1649: } jaroslav@1649: Entry refreshVersion(Version v2) { jaroslav@1649: assertNotPromise(); jaroslav@1649: @SuppressWarnings("unchecked") // if !isPromise, type is T jaroslav@1649: Entry e2 = new Entry<>(v2, (T) value); jaroslav@1649: clear(); jaroslav@1649: // value = null -- caller must drop jaroslav@1649: return e2; jaroslav@1649: } jaroslav@1649: static final Entry DEAD_ENTRY = new Entry<>(null, null); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Return the backing map associated with this type. */ jaroslav@1649: private static ClassValueMap getMap(Class type) { jaroslav@1649: // racing type.classValueMap : null (blank) => unique ClassValueMap jaroslav@1649: // if a null is observed, a map is created (lazily, synchronously, uniquely) jaroslav@1649: // all further access to that map is synchronized jaroslav@1649: ClassValueMap map = type.classValueMap; jaroslav@1649: if (map != null) return map; jaroslav@1649: return initializeMap(type); jaroslav@1649: } jaroslav@1649: jaroslav@1649: private static final Object CRITICAL_SECTION = new Object(); jaroslav@1649: private static ClassValueMap initializeMap(Class type) { jaroslav@1649: ClassValueMap map; jaroslav@1649: synchronized (CRITICAL_SECTION) { // private object to avoid deadlocks jaroslav@1649: // happens about once per type jaroslav@1649: if ((map = type.classValueMap) == null) jaroslav@1649: type.classValueMap = map = new ClassValueMap(type); jaroslav@1649: } jaroslav@1649: return map; jaroslav@1649: } jaroslav@1649: jaroslav@1649: static Entry makeEntry(Version explicitVersion, T value) { jaroslav@1649: // Note that explicitVersion might be different from this.version. jaroslav@1649: return new Entry<>(explicitVersion, value); jaroslav@1649: jaroslav@1649: // As soon as the Entry is put into the cache, the value will be jaroslav@1649: // reachable via a data race (as defined by the Java Memory Model). jaroslav@1649: // This race is benign, assuming the value object itself can be jaroslav@1649: // read safely by multiple threads. This is up to the user. jaroslav@1649: // jaroslav@1649: // The entry and version fields themselves can be safely read via jaroslav@1649: // a race because they are either final or have controlled states. jaroslav@1649: // If the pointer from the entry to the version is still null, jaroslav@1649: // or if the version goes immediately dead and is nulled out, jaroslav@1649: // the reader will take the slow path and retry under a lock. jaroslav@1649: } jaroslav@1649: jaroslav@1649: // The following class could also be top level and non-public: jaroslav@1649: jaroslav@1649: /** A backing map for all ClassValues, relative a single given type. jaroslav@1649: * Gives a fully serialized "true state" for each pair (ClassValue cv, Class type). jaroslav@1649: * Also manages an unserialized fast-path cache. jaroslav@1649: */ jaroslav@1649: static class ClassValueMap extends WeakHashMap> { jaroslav@1649: private final Class type; jaroslav@1649: private Entry[] cacheArray; jaroslav@1649: private int cacheLoad, cacheLoadLimit; jaroslav@1649: jaroslav@1649: /** Number of entries initially allocated to each type when first used with any ClassValue. jaroslav@1649: * It would be pointless to make this much smaller than the Class and ClassValueMap objects themselves. jaroslav@1649: * Must be a power of 2. jaroslav@1649: */ jaroslav@1649: private static final int INITIAL_ENTRIES = 32; jaroslav@1649: jaroslav@1649: /** Build a backing map for ClassValues, relative the given type. jaroslav@1649: * Also, create an empty cache array and install it on the class. jaroslav@1649: */ jaroslav@1649: ClassValueMap(Class type) { jaroslav@1649: this.type = type; jaroslav@1649: sizeCache(INITIAL_ENTRIES); jaroslav@1649: } jaroslav@1649: jaroslav@1649: Entry[] getCache() { return cacheArray; } jaroslav@1649: jaroslav@1649: /** Initiate a query. Store a promise (placeholder) if there is no value yet. */ jaroslav@1649: synchronized jaroslav@1649: Entry startEntry(ClassValue classValue) { jaroslav@1649: @SuppressWarnings("unchecked") // one map has entries for all value types jaroslav@1649: Entry e = (Entry) get(classValue.identity); jaroslav@1649: Version v = classValue.version(); jaroslav@1649: if (e == null) { jaroslav@1649: e = v.promise(); jaroslav@1649: // The presence of a promise means that a value is pending for v. jaroslav@1649: // Eventually, finishEntry will overwrite the promise. jaroslav@1649: put(classValue.identity, e); jaroslav@1649: // Note that the promise is never entered into the cache! jaroslav@1649: return e; jaroslav@1649: } else if (e.isPromise()) { jaroslav@1649: // Somebody else has asked the same question. jaroslav@1649: // Let the races begin! jaroslav@1649: if (e.version() != v) { jaroslav@1649: e = v.promise(); jaroslav@1649: put(classValue.identity, e); jaroslav@1649: } jaroslav@1649: return e; jaroslav@1649: } else { jaroslav@1649: // there is already a completed entry here; report it jaroslav@1649: if (e.version() != v) { jaroslav@1649: // There is a stale but valid entry here; make it fresh again. jaroslav@1649: // Once an entry is in the hash table, we don't care what its version is. jaroslav@1649: e = e.refreshVersion(v); jaroslav@1649: put(classValue.identity, e); jaroslav@1649: } jaroslav@1649: // Add to the cache, to enable the fast path, next time. jaroslav@1649: checkCacheLoad(); jaroslav@1649: addToCache(classValue, e); jaroslav@1649: return e; jaroslav@1649: } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Finish a query. Overwrite a matching placeholder. Drop stale incoming values. */ jaroslav@1649: synchronized jaroslav@1649: Entry finishEntry(ClassValue classValue, Entry e) { jaroslav@1649: @SuppressWarnings("unchecked") // one map has entries for all value types jaroslav@1649: Entry e0 = (Entry) get(classValue.identity); jaroslav@1649: if (e == e0) { jaroslav@1649: // We can get here during exception processing, unwinding from computeValue. jaroslav@1649: assert(e.isPromise()); jaroslav@1649: remove(classValue.identity); jaroslav@1649: return null; jaroslav@1649: } else if (e0 != null && e0.isPromise() && e0.version() == e.version()) { jaroslav@1649: // If e0 matches the intended entry, there has not been a remove call jaroslav@1649: // between the previous startEntry and now. So now overwrite e0. jaroslav@1649: Version v = classValue.version(); jaroslav@1649: if (e.version() != v) jaroslav@1649: e = e.refreshVersion(v); jaroslav@1649: put(classValue.identity, e); jaroslav@1649: // Add to the cache, to enable the fast path, next time. jaroslav@1649: checkCacheLoad(); jaroslav@1649: addToCache(classValue, e); jaroslav@1649: return e; jaroslav@1649: } else { jaroslav@1649: // Some sort of mismatch; caller must try again. jaroslav@1649: return null; jaroslav@1649: } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Remove an entry. */ jaroslav@1649: synchronized jaroslav@1649: void removeEntry(ClassValue classValue) { jaroslav@1649: Entry e = remove(classValue.identity); jaroslav@1649: if (e == null) { jaroslav@1649: // Uninitialized, and no pending calls to computeValue. No change. jaroslav@1649: } else if (e.isPromise()) { jaroslav@1649: // State is uninitialized, with a pending call to finishEntry. jaroslav@1649: // Since remove is a no-op in such a state, keep the promise jaroslav@1649: // by putting it back into the map. jaroslav@1649: put(classValue.identity, e); jaroslav@1649: } else { jaroslav@1649: // In an initialized state. Bump forward, and de-initialize. jaroslav@1649: classValue.bumpVersion(); jaroslav@1649: // Make all cache elements for this guy go stale. jaroslav@1649: removeStaleEntries(classValue); jaroslav@1649: } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Change the value for an entry. */ jaroslav@1649: synchronized jaroslav@1649: void changeEntry(ClassValue classValue, T value) { jaroslav@1649: @SuppressWarnings("unchecked") // one map has entries for all value types jaroslav@1649: Entry e0 = (Entry) get(classValue.identity); jaroslav@1649: Version version = classValue.version(); jaroslav@1649: if (e0 != null) { jaroslav@1649: if (e0.version() == version && e0.value() == value) jaroslav@1649: // no value change => no version change needed jaroslav@1649: return; jaroslav@1649: classValue.bumpVersion(); jaroslav@1649: removeStaleEntries(classValue); jaroslav@1649: } jaroslav@1649: Entry e = makeEntry(version, value); jaroslav@1649: put(classValue.identity, e); jaroslav@1649: // Add to the cache, to enable the fast path, next time. jaroslav@1649: checkCacheLoad(); jaroslav@1649: addToCache(classValue, e); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /// -------- jaroslav@1649: /// Cache management. jaroslav@1649: /// -------- jaroslav@1649: jaroslav@1649: // Statics do not need synchronization. jaroslav@1649: jaroslav@1649: /** Load the cache entry at the given (hashed) location. */ jaroslav@1649: static Entry loadFromCache(Entry[] cache, int i) { jaroslav@1649: // non-racing cache.length : constant jaroslav@1649: // racing cache[i & (mask)] : null <=> Entry jaroslav@1649: return cache[i & (cache.length-1)]; jaroslav@1649: // invariant: returned value is null or well-constructed (ready to match) jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Look in the cache, at the home location for the given ClassValue. */ jaroslav@1649: static Entry probeHomeLocation(Entry[] cache, ClassValue classValue) { jaroslav@1649: return classValue.castEntry(loadFromCache(cache, classValue.hashCodeForCache)); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Given that first probe was a collision, retry at nearby locations. */ jaroslav@1649: static Entry probeBackupLocations(Entry[] cache, ClassValue classValue) { jaroslav@1649: if (PROBE_LIMIT <= 0) return null; jaroslav@1649: // Probe the cache carefully, in a range of slots. jaroslav@1649: int mask = (cache.length-1); jaroslav@1649: int home = (classValue.hashCodeForCache & mask); jaroslav@1649: Entry e2 = cache[home]; // victim, if we find the real guy jaroslav@1649: if (e2 == null) { jaroslav@1649: return null; // if nobody is at home, no need to search nearby jaroslav@1649: } jaroslav@1649: // assume !classValue.match(e2), but do not assert, because of races jaroslav@1649: int pos2 = -1; jaroslav@1649: for (int i = home + 1; i < home + PROBE_LIMIT; i++) { jaroslav@1649: Entry e = cache[i & mask]; jaroslav@1649: if (e == null) { jaroslav@1649: break; // only search within non-null runs jaroslav@1649: } jaroslav@1649: if (classValue.match(e)) { jaroslav@1649: // relocate colliding entry e2 (from cache[home]) to first empty slot jaroslav@1649: cache[home] = e; jaroslav@1649: if (pos2 >= 0) { jaroslav@1649: cache[i & mask] = Entry.DEAD_ENTRY; jaroslav@1649: } else { jaroslav@1649: pos2 = i; jaroslav@1649: } jaroslav@1649: cache[pos2 & mask] = ((entryDislocation(cache, pos2, e2) < PROBE_LIMIT) jaroslav@1649: ? e2 // put e2 here if it fits jaroslav@1649: : Entry.DEAD_ENTRY); jaroslav@1649: return classValue.castEntry(e); jaroslav@1649: } jaroslav@1649: // Remember first empty slot, if any: jaroslav@1649: if (!e.isLive() && pos2 < 0) pos2 = i; jaroslav@1649: } jaroslav@1649: return null; jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** How far out of place is e? */ jaroslav@1649: private static int entryDislocation(Entry[] cache, int pos, Entry e) { jaroslav@1649: ClassValue cv = e.classValueOrNull(); jaroslav@1649: if (cv == null) return 0; // entry is not live! jaroslav@1649: int mask = (cache.length-1); jaroslav@1649: return (pos - cv.hashCodeForCache) & mask; jaroslav@1649: } jaroslav@1649: jaroslav@1649: /// -------- jaroslav@1649: /// Below this line all functions are private, and assume synchronized access. jaroslav@1649: /// -------- jaroslav@1649: jaroslav@1649: private void sizeCache(int length) { jaroslav@1649: assert((length & (length-1)) == 0); // must be power of 2 jaroslav@1649: cacheLoad = 0; jaroslav@1649: cacheLoadLimit = (int) ((double) length * CACHE_LOAD_LIMIT / 100); jaroslav@1649: cacheArray = new Entry[length]; jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Make sure the cache load stays below its limit, if possible. */ jaroslav@1649: private void checkCacheLoad() { jaroslav@1649: if (cacheLoad >= cacheLoadLimit) { jaroslav@1649: reduceCacheLoad(); jaroslav@1649: } jaroslav@1649: } jaroslav@1649: private void reduceCacheLoad() { jaroslav@1649: removeStaleEntries(); jaroslav@1649: if (cacheLoad < cacheLoadLimit) jaroslav@1649: return; // win jaroslav@1649: Entry[] oldCache = getCache(); jaroslav@1649: if (oldCache.length > HASH_MASK) jaroslav@1649: return; // lose jaroslav@1649: sizeCache(oldCache.length * 2); jaroslav@1649: for (Entry e : oldCache) { jaroslav@1649: if (e != null && e.isLive()) { jaroslav@1649: addToCache(e); jaroslav@1649: } jaroslav@1649: } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Remove stale entries in the given range. jaroslav@1649: * Should be executed under a Map lock. jaroslav@1649: */ jaroslav@1649: private void removeStaleEntries(Entry[] cache, int begin, int count) { jaroslav@1649: if (PROBE_LIMIT <= 0) return; jaroslav@1649: int mask = (cache.length-1); jaroslav@1649: int removed = 0; jaroslav@1649: for (int i = begin; i < begin + count; i++) { jaroslav@1649: Entry e = cache[i & mask]; jaroslav@1649: if (e == null || e.isLive()) jaroslav@1649: continue; // skip null and live entries jaroslav@1649: Entry replacement = null; jaroslav@1649: if (PROBE_LIMIT > 1) { jaroslav@1649: // avoid breaking up a non-null run jaroslav@1649: replacement = findReplacement(cache, i); jaroslav@1649: } jaroslav@1649: cache[i & mask] = replacement; jaroslav@1649: if (replacement == null) removed += 1; jaroslav@1649: } jaroslav@1649: cacheLoad = Math.max(0, cacheLoad - removed); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Clearing a cache slot risks disconnecting following entries jaroslav@1649: * from the head of a non-null run, which would allow them jaroslav@1649: * to be found via reprobes. Find an entry after cache[begin] jaroslav@1649: * to plug into the hole, or return null if none is needed. jaroslav@1649: */ jaroslav@1649: private Entry findReplacement(Entry[] cache, int home1) { jaroslav@1649: Entry replacement = null; jaroslav@1649: int haveReplacement = -1, replacementPos = 0; jaroslav@1649: int mask = (cache.length-1); jaroslav@1649: for (int i2 = home1 + 1; i2 < home1 + PROBE_LIMIT; i2++) { jaroslav@1649: Entry e2 = cache[i2 & mask]; jaroslav@1649: if (e2 == null) break; // End of non-null run. jaroslav@1649: if (!e2.isLive()) continue; // Doomed anyway. jaroslav@1649: int dis2 = entryDislocation(cache, i2, e2); jaroslav@1649: if (dis2 == 0) continue; // e2 already optimally placed jaroslav@1649: int home2 = i2 - dis2; jaroslav@1649: if (home2 <= home1) { jaroslav@1649: // e2 can replace entry at cache[home1] jaroslav@1649: if (home2 == home1) { jaroslav@1649: // Put e2 exactly where he belongs. jaroslav@1649: haveReplacement = 1; jaroslav@1649: replacementPos = i2; jaroslav@1649: replacement = e2; jaroslav@1649: } else if (haveReplacement <= 0) { jaroslav@1649: haveReplacement = 0; jaroslav@1649: replacementPos = i2; jaroslav@1649: replacement = e2; jaroslav@1649: } jaroslav@1649: // And keep going, so we can favor larger dislocations. jaroslav@1649: } jaroslav@1649: } jaroslav@1649: if (haveReplacement >= 0) { jaroslav@1649: if (cache[(replacementPos+1) & mask] != null) { jaroslav@1649: // Be conservative, to avoid breaking up a non-null run. jaroslav@1649: cache[replacementPos & mask] = (Entry) Entry.DEAD_ENTRY; jaroslav@1649: } else { jaroslav@1649: cache[replacementPos & mask] = null; jaroslav@1649: cacheLoad -= 1; jaroslav@1649: } jaroslav@1649: } jaroslav@1649: return replacement; jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Remove stale entries in the range near classValue. */ jaroslav@1649: private void removeStaleEntries(ClassValue classValue) { jaroslav@1649: removeStaleEntries(getCache(), classValue.hashCodeForCache, PROBE_LIMIT); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Remove all stale entries, everywhere. */ jaroslav@1649: private void removeStaleEntries() { jaroslav@1649: Entry[] cache = getCache(); jaroslav@1649: removeStaleEntries(cache, 0, cache.length + PROBE_LIMIT - 1); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Add the given entry to the cache, in its home location, unless it is out of date. */ jaroslav@1649: private void addToCache(Entry e) { jaroslav@1649: ClassValue classValue = e.classValueOrNull(); jaroslav@1649: if (classValue != null) jaroslav@1649: addToCache(classValue, e); jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Add the given entry to the cache, in its home location. */ jaroslav@1649: private void addToCache(ClassValue classValue, Entry e) { jaroslav@1649: if (PROBE_LIMIT <= 0) return; // do not fill cache jaroslav@1649: // Add e to the cache. jaroslav@1649: Entry[] cache = getCache(); jaroslav@1649: int mask = (cache.length-1); jaroslav@1649: int home = classValue.hashCodeForCache & mask; jaroslav@1649: Entry e2 = placeInCache(cache, home, e, false); jaroslav@1649: if (e2 == null) return; // done jaroslav@1649: if (PROBE_LIMIT > 1) { jaroslav@1649: // try to move e2 somewhere else in his probe range jaroslav@1649: int dis2 = entryDislocation(cache, home, e2); jaroslav@1649: int home2 = home - dis2; jaroslav@1649: for (int i2 = home2; i2 < home2 + PROBE_LIMIT; i2++) { jaroslav@1649: if (placeInCache(cache, i2 & mask, e2, true) == null) { jaroslav@1649: return; jaroslav@1649: } jaroslav@1649: } jaroslav@1649: } jaroslav@1649: // Note: At this point, e2 is just dropped from the cache. jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Store the given entry. Update cacheLoad, and return any live victim. jaroslav@1649: * 'Gently' means return self rather than dislocating a live victim. jaroslav@1649: */ jaroslav@1649: private Entry placeInCache(Entry[] cache, int pos, Entry e, boolean gently) { jaroslav@1649: Entry e2 = overwrittenEntry(cache[pos]); jaroslav@1649: if (gently && e2 != null) { jaroslav@1649: // do not overwrite a live entry jaroslav@1649: return e; jaroslav@1649: } else { jaroslav@1649: cache[pos] = e; jaroslav@1649: return e2; jaroslav@1649: } jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Note an entry that is about to be overwritten. jaroslav@1649: * If it is not live, quietly replace it by null. jaroslav@1649: * If it is an actual null, increment cacheLoad, jaroslav@1649: * because the caller is going to store something jaroslav@1649: * in its place. jaroslav@1649: */ jaroslav@1649: private Entry overwrittenEntry(Entry e2) { jaroslav@1649: if (e2 == null) cacheLoad += 1; jaroslav@1649: else if (e2.isLive()) return e2; jaroslav@1649: return null; jaroslav@1649: } jaroslav@1649: jaroslav@1649: /** Percent loading of cache before resize. */ jaroslav@1649: private static final int CACHE_LOAD_LIMIT = 67; // 0..100 jaroslav@1649: /** Maximum number of probes to attempt. */ jaroslav@1649: private static final int PROBE_LIMIT = 6; // 1.. jaroslav@1649: // N.B. Set PROBE_LIMIT=0 to disable all fast paths. jaroslav@1649: } jaroslav@1649: }