if (actualCause[0] == RemovalCause.EXPIRED) { boolean expired = false; if (expiresAfterAccess()) { expired |= ((now - n.getAccessTime()) >= expiresAfterAccessNanos()); if (expiresAfterWrite()) { expired |= ((now - n.getWriteTime()) >= expiresAfterWriteNanos()); if (expiresVariable()) { expired |= (n.getVariableTime() <= now); makeDead(n); if (node.inEden() && (evicts() || expiresAfterAccess())) { accessOrderEdenDeque().remove(node); } else if (evicts()) { if (node.inMainProbation()) { accessOrderProbationDeque().remove(node); } else { accessOrderProtectedDeque().remove(node); if (expiresAfterWrite()) { writeOrderDeque().remove(node); } else if (expiresVariable()) { timerWheel().deschedule(node); statsCounter().recordEviction(node.getWeight()); if (hasRemovalListener()) {
oldWeight[0] = n.getWeight(); if ((nodeKey[0] == null) || (oldValue[0] == null) || hasExpired(n, now[0] = expirationTicker().read())) { oldValue[0] = null; return n; long varTime = expireAfterUpdate(n, key, value, expiry(), now[0]); if (value != oldValue[0]) { writer.write(nodeKey[0], value); n.setValue(value, valueReferenceQueue()); n.setWeight(weight); setVariableTime(n, varTime); setAccessTime(n, now[0]); setWriteTime(n, now[0]); return n; if (expiresAfterWrite() || (weightedDifference != 0)) { afterWrite(new UpdateTask(node, weightedDifference)); } else { afterRead(node, now[0], /* recordHit */ false); if (hasRemovalListener() && (value != oldValue[0])) { notifyRemoval(nodeKey[0], oldValue[0], RemovalCause.REPLACED);
/** * Performs the post-processing work required after a write. * * @param task the pending operation to be applied */ void afterWrite(Runnable task) { if (buffersWrites()) { for (int i = 0; i < WRITE_BUFFER_RETRIES; i++) { if (writeBuffer().offer(task)) { scheduleAfterWrite(); return; } scheduleDrainBuffers(); } // The maintenance task may be scheduled but not running due to all of the executor's threads // being busy. If all of the threads are writing into the cache then no progress can be made // without assistance. try { performCleanUp(task); } catch (RuntimeException e) { logger.log(Level.SEVERE, "Exception thrown when performing the maintenance task", e); } } else { scheduleAfterWrite(); } }
/** Expires entries in the access-order queue. */ @GuardedBy("evictionLock") void expireAfterAccessEntries(long now) { if (!expiresAfterAccess()) { return; } expireAfterAccessEntries(accessOrderEdenDeque(), now); if (evicts()) { expireAfterAccessEntries(accessOrderProbationDeque(), now); expireAfterAccessEntries(accessOrderProtectedDeque(), now); } }
/** Drains the weak key references queue. */ @GuardedBy("evictionLock") void drainKeyReferences() { if (!collectKeys()) { return; } Reference<? extends K> keyRef; while ((keyRef = keyReferenceQueue().poll()) != null) { Node<K, V> node = data.get(keyRef); if (node != null) { evictEntry(node, RemovalCause.COLLECTED, 0L); } } }
now[0] = expirationTicker().read(); weight[1] = weigher.weigh(key, newValue[0]); n = nodeFactory.newNode(keyRef, newValue[0], valueReferenceQueue(), weight[1], now[0]); setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0])); return n; if ((nodeKey[0] == null) || (oldValue[0] == null)) { cause[0] = RemovalCause.COLLECTED; } else if (hasExpired(n, now[0])) { cause[0] = RemovalCause.EXPIRED; now[0] = expirationTicker().read(); if (cause[0] == null) { if (newValue[0] != oldValue[0]) { cause[0] = RemovalCause.REPLACED; setVariableTime(n, expireAfterUpdate(n, key, newValue[0], expiry(), now[0])); } else { setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0])); n.setValue(newValue[0], valueReferenceQueue()); n.setWeight(weight[1]); setAccessTime(n, now[0]); setWriteTime(n, now[0]); return n; statsCounter().recordEviction(weight[0]);
@Override public @Nullable V getIfPresent(Object key, boolean recordStats) { Node<K, V> node = data.get(nodeFactory.newLookupKey(key)); if (node == null) { if (recordStats) { statsCounter().recordMisses(1); } return null; } V value = node.getValue(); long now = expirationTicker().read(); if (hasExpired(node, now) || (collectValues() && (value == null))) { if (recordStats) { statsCounter().recordMisses(1); } scheduleDrainBuffers(); return null; } if (!isComputingAsync(node)) { @SuppressWarnings("unchecked") K castedKey = (K) key; setAccessTime(node, now); setVariableTime(node, expireAfterRead(node, castedKey, value, expiry(), now)); } afterRead(node, now, recordStats); return value; }
now[0] = expirationTicker().read(); weight[1] = weigher.weigh(key, newValue[0]); n = nodeFactory.newNode(key, keyReferenceQueue(), newValue[0], valueReferenceQueue(), weight[1], now[0]); setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0])); return n; if ((nodeKey[0] == null) || (oldValue[0] == null)) { cause[0] = RemovalCause.COLLECTED; } else if (hasExpired(n, now[0])) { cause[0] = RemovalCause.EXPIRED; } else { n.setValue(newValue[0], valueReferenceQueue()); n.setWeight(weight[1]); now[0] = expirationTicker().read(); setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0])); setAccessTime(n, now[0]); setWriteTime(n, now[0]); return n; afterWrite(new RemovalTask(removed[0])); if (hasRemovalListener()) { notifyRemoval(nodeKey[0], oldValue[0], cause[0]); statsCounter().recordEviction(weight[0]);
long now = expirationTicker().read(); int newWeight = weigher.weigh(key, value); for (;;) { if (prior == null) { if (node == null) { node = nodeFactory.newNode(key, keyReferenceQueue(), value, valueReferenceQueue(), newWeight, now); setVariableTime(node, expireAfterCreate(key, value, expiry, now)); if (notifyWriter && hasWriter()) { Node<K, V> computed = node; prior = data.computeIfAbsent(node.getKeyReference(), k -> { }); if (prior == node) { afterWrite(new AddTask(node, newWeight)); return null; prior = data.putIfAbsent(node.getKeyReference(), node); if (prior == null) { afterWrite(new AddTask(node, newWeight)); return null; oldWeight = prior.getWeight(); if (oldValue == null) { varTime = expireAfterCreate(key, value, expiry, now); writer.delete(key, null, RemovalCause.COLLECTED); } else if (hasExpired(prior, now)) {
} else if (hasExpired(n, now)) { cause[0] = RemovalCause.EXPIRED; } else { writer.delete(key, value[0], cause[0]); makeDead(n); return null; if (node.inEden() && (evicts() || expiresAfterAccess())) { accessOrderEdenDeque().remove(node); } else if (evicts()) { if (node.inMainProbation()) { accessOrderProbationDeque().remove(node); } else { accessOrderProtectedDeque().remove(node); if (expiresAfterWrite()) { writeOrderDeque().remove(node); } else if (expiresVariable()) { timerWheel().deschedule(node); if ((cause[0] != null) && hasRemovalListener()) { notifyRemoval(key, value[0], cause[0]);
/** Creates a serialization proxy based on the common configuration shared by all cache types. */ static <K, V> SerializationProxy<K, V> makeSerializationProxy( BoundedLocalCache<?, ?> cache, boolean isWeighted) { SerializationProxy<K, V> proxy = new SerializationProxy<>(); proxy.weakKeys = cache.collectKeys(); proxy.weakValues = cache.nodeFactory.weakValues(); proxy.softValues = cache.nodeFactory.softValues(); proxy.isRecordingStats = cache.isRecordingStats(); proxy.removalListener = cache.removalListener(); proxy.ticker = cache.expirationTicker(); proxy.writer = cache.writer; if (cache.expiresAfterAccess()) { proxy.expiresAfterAccessNanos = cache.expiresAfterAccessNanos(); } if (cache.expiresAfterWrite()) { proxy.expiresAfterWriteNanos = cache.expiresAfterWriteNanos(); } if (cache.expiresVariable()) { proxy.expiry = cache.expiry(); } if (cache.evicts()) { if (isWeighted) { proxy.weigher = cache.weigher; proxy.maximumWeight = cache.maximum(); } else { proxy.maximumSize = cache.maximum(); } } return proxy; }
@Override public @Nullable V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction, boolean recordStats, boolean recordLoad) { requireNonNull(key); requireNonNull(mappingFunction); long now = expirationTicker().read(); // An optimistic fast path to avoid unnecessary locking Node<K, V> node = data.get(nodeFactory.newLookupKey(key)); if (node != null) { V value = node.getValue(); if ((value != null) && !hasExpired(node, now)) { if (!isComputingAsync(node)) { setVariableTime(node, expireAfterRead(node, key, value, expiry(), now)); setAccessTime(node, now); } afterRead(node, now, /* recordHit */ true); return value; } } if (recordStats) { mappingFunction = statsAware(mappingFunction, recordLoad); } Object keyRef = nodeFactory.newReferenceKey(key, keyReferenceQueue()); return doComputeIfAbsent(key, keyRef, mappingFunction, new long[] { now }); }
/** Updates the node's location in the page replacement policy. */ @GuardedBy("evictionLock") void onAccess(Node<K, V> node) { if (evicts()) { K key = node.getKey(); if (key == null) { return; } frequencySketch().increment(key); if (node.inEden()) { reorder(accessOrderEdenDeque(), node); } else if (node.inMainProbation()) { reorderProbation(node); } else { reorder(accessOrderProtectedDeque(), node); } } else if (expiresAfterAccess()) { reorder(accessOrderEdenDeque(), node); } if (expiresVariable()) { timerWheel().reschedule(node); } }
if (oldValue == null) { cause = RemovalCause.COLLECTED; } else if (hasExpired(node, expirationTicker().read())) { cause = RemovalCause.EXPIRED; } else { if (hasRemovalListener()) { @SuppressWarnings("unchecked") K castKey = (K) key; notifyRemoval(castKey, oldValue, cause); afterWrite(new RemovalTask(node)); return (cause == RemovalCause.EXPLICIT) ? oldValue : null;
void evictFromMain(int candidates) { int victimQueue = PROBATION; Node<K, V> victim = accessOrderProbationDeque().peekFirst(); Node<K, V> candidate = accessOrderProbationDeque().peekLast(); while (weightedSize() > maximum()) { victim = accessOrderProtectedDeque().peekFirst(); victimQueue = PROTECTED; continue; } else if (victimQueue == PROTECTED) { victim = accessOrderEdenDeque().peekFirst(); victimQueue = EDEN; continue; candidate = previous; candidates--; evictEntry(evict, RemovalCause.SIZE, 0L); continue; } else if (candidate == null) { Node<K, V> evict = victim; victim = victim.getNextInAccessOrder(); evictEntry(evict, RemovalCause.SIZE, 0L); continue; @NonNull Node<K, V> evict = victim; victim = victim.getNextInAccessOrder(); evictEntry(evict, RemovalCause.COLLECTED, 0L); continue; } else if (candidateKey == null) {
private void checkEvictionDeque(BoundedLocalCache<K, V> cache) { if (cache.evicts()) { ImmutableList<LinkedDeque<Node<K, V>>> deques = ImmutableList.of( cache.accessOrderEdenDeque(), cache.accessOrderProbationDeque(), cache.accessOrderProtectedDeque()); checkLinks(cache, deques, desc); checkDeque(cache.accessOrderEdenDeque(), desc); checkDeque(cache.accessOrderProbationDeque(), desc); } else if (cache.expiresAfterAccess()) { checkLinks(cache, ImmutableList.of(cache.accessOrderEdenDeque()), desc); checkDeque(cache.accessOrderEdenDeque(), desc); } if (cache.expiresAfterWrite()) { checkLinks(cache, ImmutableList.of(cache.writeOrderDeque()), desc); checkDeque(cache.writeOrderDeque(), desc); } }
if (cache.collectKeys()) { if ((key != null) && (value != null)) { desc.expectThat("inconsistent", cache.containsKey(key), is(true)); if (!cache.collectValues()) { desc.expectThat("not null value", value, is(not(nullValue()))); if ((key != null) && !cache.hasExpired(node, cache.expirationTicker().read())) { desc.expectThat(() -> "Could not find key: " + key + ", value: " + value, cache.containsValue(value), is(true)); if (cache.refreshAfterWrite()) { desc.expectThat("infinite timestamp", node.getWriteTime(), is(not(Long.MAX_VALUE)));
/** Creates an instance based on the builder's configuration. */ protected BoundedLocalCache(Caffeine<K, V> builder, @Nullable CacheLoader<K, V> cacheLoader, boolean isAsync) { this.isAsync = isAsync; this.cacheLoader = cacheLoader; executor = builder.getExecutor(); writer = builder.getCacheWriter(); evictionLock = new ReentrantLock(); weigher = builder.getWeigher(isAsync); drainBuffersTask = new PerformCleanupTask(); nodeFactory = NodeFactory.newFactory(builder, isAsync); data = new ConcurrentHashMap<>(builder.getInitialCapacity()); readBuffer = evicts() || collectKeys() || collectValues() || expiresAfterAccess() ? new BoundedBuffer<>() : Buffer.disabled(); accessPolicy = (evicts() || expiresAfterAccess()) ? this::onAccess : e -> {}; if (evicts()) { setMaximum(builder.getMaximum()); } }
/** * Returns an unmodifiable snapshot map ordered in eviction order, either ascending or descending. * Beware that obtaining the mappings is <em>NOT</em> a constant-time operation. * * @param limit the maximum number of entries * @param transformer a function that unwraps the value * @param hottest the iteration order * @return an unmodifiable snapshot in a specified order */ @SuppressWarnings("GuardedByChecker") Map<K, V> evictionOrder(int limit, Function<V, V> transformer, boolean hottest) { Supplier<Iterator<Node<K, V>>> iteratorSupplier = () -> { Comparator<Node<K, V>> comparator = Comparator.comparingInt(node -> { K key = node.getKey(); return (key == null) ? 0 : frequencySketch().frequency(key); }); if (hottest) { PeekingIterator<Node<K, V>> secondary = PeekingIterator.comparing( accessOrderProbationDeque().descendingIterator(), accessOrderEdenDeque().descendingIterator(), comparator); return PeekingIterator.concat(accessOrderProtectedDeque().descendingIterator(), secondary); } else { PeekingIterator<Node<K, V>> primary = PeekingIterator.comparing( accessOrderEdenDeque().iterator(), accessOrderProbationDeque().iterator(), comparator.reversed()); return PeekingIterator.concat(primary, accessOrderProtectedDeque().iterator()); } }; return fixedSnapshot(iteratorSupplier, limit, transformer); }
if (!evicts()) { Supplier<Iterator<Node<K, V>>> iteratorSupplier = () -> oldest ? accessOrderEdenDeque().iterator() : accessOrderEdenDeque().descendingIterator(); return fixedSnapshot(iteratorSupplier, limit, transformer); PeekingIterator<Node<K, V>> first, second, third; if (oldest) { first = accessOrderEdenDeque().iterator(); second = accessOrderProbationDeque().iterator(); third = accessOrderProtectedDeque().iterator(); } else { comparator = comparator.reversed(); first = accessOrderEdenDeque().descendingIterator(); second = accessOrderProbationDeque().descendingIterator(); third = accessOrderProtectedDeque().descendingIterator(); return fixedSnapshot(iteratorSupplier, limit, transformer);