@Nullable public ListenableFuture<V> loadFuture(@Nonnull K key, @Nonnull CacheLoader<? super K, V> loader) { try { stopwatch.start(); V previousValue = oldValue.get(); if (previousValue == null) { V newValue = loader.load(key); return set(newValue) ? futureValue : Futures.immediateFuture(newValue); } ListenableFuture<V> newValue = loader.reload(key, previousValue); if (newValue == null) { return Futures.immediateFuture(null); } // To avoid a race, make sure the refreshed value is set into loadingValueReference // *before* returning newValue from the cache query. return Futures.transform(newValue, new Function<V, V>() { @Override public V apply(V newValue) { LoadingValueReference.this.set(newValue); return newValue; } }); } catch (Throwable t) { ListenableFuture<V> result = setException(t) ? futureValue : fullyFailedFuture(t); if (t instanceof InterruptedException) { Thread.currentThread().interrupt(); } return result; } }
@Nonnull ReferenceEntry<K, V> getNextEvictable() { for (ReferenceEntry<K, V> e : accessQueue) { int weight = e.getValueReference().getWeight(); if (weight > 0) { return e; } } throw new AssertionError(); }
void clear() { if (count != 0) { // read-volatile lock(); try { AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table; for (int i = 0; i < table.length(); ++i) { for (ReferenceEntry<K, V> e = table.get(i); e != null; e = e.getNext()) { // Loading references aren't actually in the map yet. if (e.getValueReference().isActive()) { enqueueNotification(e, RemovalCause.EXPLICIT); } } } for (int i = 0; i < table.length(); ++i) { table.set(i, null); } clearReferenceQueues(); writeQueue.clear(); accessQueue.clear(); readCount.set(0); ++modCount; count = 0; // write-volatile } finally { unlock(); postWriteCleanup(); } } }
&& map.keyEquivalence.equivalent(key, entryKey)) { ValueReference<K, V> valueReference = e.getValueReference(); V entryValue = valueReference.get();
if (valueReference.isLoading() || (checkTime && (now - e.getWriteTime() < map.refreshNanos))) {
if (valueReference.isLoading()) { return waitForLoadingValue(e, key, valueReference);
/** * Performs eviction if the segment is over capacity. Avoids flushing the entire cache if the * newest entry exceeds the maximum weight all on its own. * * @param newest the most recently added entry */ void evictEntries(@Nonnull ReferenceEntry<K, V> newest) { if (!map.evictsBySize()) { return; } drainRecencyQueue(); // If the newest entry by itself is too heavy for the segment, don't bother evicting // anything else, just that if (newest.getValueReference().getWeight() > maxSegmentWeight) { if (!removeEntry(newest, newest.getHash(), RemovalCause.SIZE)) { throw new AssertionError(); } } while (totalWeight > maxSegmentWeight) { ReferenceEntry<K, V> e = getNextEvictable(); if (!removeEntry(e, e.getHash(), RemovalCause.SIZE)) { throw new AssertionError(); } } }
/** * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, * loading, or expired. */ @Nullable V getLiveValue(@Nonnull ReferenceEntry<K, V> entry, long now) { if (entry.getKey() == null) { tryDrainReferenceQueues(); return null; } V value = entry.getValueReference().get(); if (value == null) { tryDrainReferenceQueues(); return null; } if (map.isExpired(entry, now)) { tryExpireEntries(now); return null; } return value; }
@Nullable V get(Object key, int hash) { try { if (count != 0) { // read-volatile long now = map.ticker.read(); ReferenceEntry<K, V> e = getLiveEntry(key, hash, now); if (e == null) { return null; } V value = e.getValueReference().get(); if (value != null) { recordRead(e, now); return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader); } tryDrainReferenceQueues(); } return null; } finally { postReadCleanup(); } }
@Nullable V waitForLoadingValue(@Nonnull ReferenceEntry<K, V> e, K key, @Nonnull ValueReference<K, V> valueReference) throws ExecutionException { if (!valueReference.isLoading()) { throw new AssertionError(); } Preconditions.checkState(!Thread.holdsLock(e), "Recursive load of: %s", key); // don't consider expiration as we're concurrent with loading try { V value = valueReference.waitForValue(); if (value == null) { throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + "."); } // re-read ticker now that loading has completed long now = map.ticker.read(); recordRead(e, now); return value; } finally { } }
/** * Gets the value from an entry. Returns null if the entry is invalid, partially-collected, * loading, or expired. Unlike {@link Segment#getLiveValue} this method does not attempt to * cleanup stale entries. As such it should only be called outside of a segment context, such as * during iteration. */ @Nullable V getLiveValue(@Nonnull ReferenceEntry<K, V> entry, long now) { if (entry.getKey() == null) { return null; } V value = entry.getValueReference().get(); if (value == null) { return null; } if (isExpired(entry, now)) { return null; } return value; }
/** * Copies {@code original} into a new entry chained to {@code newNext}. Returns the new entry, * or {@code null} if {@code original} was already garbage collected. */ @Nullable ReferenceEntry<K, V> copyEntry(@Nonnull ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) { if (original.getKey() == null) { // key collected return null; } ValueReference<K, V> valueReference = original.getValueReference(); V value = valueReference.get(); if ((value == null) && valueReference.isActive()) { // value collected return null; } ReferenceEntry<K, V> newEntry = map.entryFactory.copyEntry(this, original, newNext); newEntry.setValueReference(valueReference.copyFor(this.valueReferenceQueue, value, newEntry)); return newEntry; }
@Nullable ReferenceEntry<K, V> removeValueFromChain(ReferenceEntry<K, V> first, @Nonnull ReferenceEntry<K, V> entry, K key, int hash, @Nonnull ValueReference<K, V> valueReference, RemovalCause cause) { enqueueNotification(key, hash, valueReference, cause); writeQueue.remove(entry); accessQueue.remove(entry); if (valueReference.isLoading()) { valueReference.notifyNewValue(null); return first; } else { return removeEntryFromChain(first, entry); } }
boolean containsKey(Object key, int hash) { try { if (count != 0) { // read-volatile long now = map.ticker.read(); ReferenceEntry<K, V> e = getLiveEntry(key, hash, now); if (e == null) { return false; } return e.getValueReference().get() != null; } return false; } finally { postReadCleanup(); } }