@Override public boolean contains(Object obj) { if (!(obj instanceof Entry<?, ?>)) { return false; } Entry<?, ?> entry = (Entry<?, ?>) obj; Node<K, V> node = cache.data.get(cache.nodeFactory.newLookupKey(entry.getKey())); return (node != null) && Objects.equals(node.getValue(), entry.getValue()); }
@Test(dataProvider = "caches") @CacheSpec(compute = Compute.SYNC, implementation = Implementation.Caffeine, population = Population.EMPTY, maximumSize = Maximum.FULL) public void exceedsMaximumBufferSize_onRead(Cache<Integer, Integer> cache, CacheContext context) { BoundedLocalCache<Integer, Integer> localCache = asBoundedLocalCache(cache); Node<Integer, Integer> dummy = localCache.nodeFactory.newNode( new WeakKeyReference<>(null, null), null, null, 1, 0); localCache.frequencySketch().ensureCapacity(1); Buffer<Node<Integer, Integer>> buffer = localCache.readBuffer; for (int i = 0; i < BoundedBuffer.BUFFER_SIZE; i++) { buffer.offer(dummy); } assertThat(buffer.offer(dummy), is(Buffer.FULL)); localCache.afterRead(dummy, 0, /* recordHit */ true); assertThat(buffer.offer(dummy), is(not(Buffer.FULL))); }
/** 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()); } }
@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 }); }
/** 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; }
int newWeight = weigher.weigh(key, value); for (;;) { Node<K, V> prior = data.get(nodeFactory.newLookupKey(key)); if (prior == null) { if (node == null) { node = nodeFactory.newNode(key, keyReferenceQueue(), value, valueReferenceQueue(), newWeight, now); setVariableTime(node, expireAfterCreate(key, value, expiry, now));
@Override public @Nullable V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { requireNonNull(key); requireNonNull(value); requireNonNull(remappingFunction); long[] now = { expirationTicker().read() }; Object keyRef = nodeFactory.newReferenceKey(key, keyReferenceQueue()); BiFunction<? super K, ? super V, ? extends V> mergeFunction = (k, oldValue) -> (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value); return remap(key, keyRef, mergeFunction, now, /* computeIfAbsent */ true); }
int newWeight = weigher.weigh(key, value); for (;;) { Node<K, V> prior = data.get(nodeFactory.newLookupKey(key)); if (prior == null) { if (node == null) { node = nodeFactory.newNode(key, keyReferenceQueue(), value, valueReferenceQueue(), newWeight, now); setVariableTime(node, expireAfterCreate(key, value, expiry, now));
@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 }); }
/** 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 compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction, boolean recordMiss, boolean recordLoad) { requireNonNull(key); requireNonNull(remappingFunction); long[] now = { expirationTicker().read() }; Object keyRef = nodeFactory.newReferenceKey(key, keyReferenceQueue()); BiFunction<? super K, ? super V, ? extends V> statsAwareRemappingFunction = statsAware(remappingFunction, recordMiss, recordLoad); return remap(key, keyRef, statsAwareRemappingFunction, now, /* computeIfAbsent */ true); }
@Override public boolean containsKey(Object key) { Node<K, V> node = data.get(nodeFactory.newLookupKey(key)); return (node != null) && (node.getValue() != null) && !hasExpired(node, expirationTicker().read()); }
n = nodeFactory.newNode(key, keyReferenceQueue(), newValue[0], valueReferenceQueue(), weight[1], now[0]); setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0]));
@Override public @Nullable V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) { requireNonNull(key); requireNonNull(value); requireNonNull(remappingFunction); long[] now = { expirationTicker().read() }; Object keyRef = nodeFactory.newReferenceKey(key, keyReferenceQueue()); BiFunction<? super K, ? super V, ? extends V> mergeFunction = (k, oldValue) -> (oldValue == null) ? value : statsAware(remappingFunction).apply(oldValue, value); return remap(key, keyRef, mergeFunction, now, /* computeIfAbsent */ true); }
/** 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()); } }
@Override public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) { requireNonNull(function); BiFunction<K, V, V> remappingFunction = (key, oldValue) -> { V newValue = requireNonNull(function.apply(key, oldValue)); if (oldValue != newValue) { writer.write(key, newValue); } return newValue; }; for (K key : keySet()) { long[] now = { expirationTicker().read() }; Object lookupKey = nodeFactory.newLookupKey(key); remap(key, lookupKey, remappingFunction, now, /* computeIfAbsent */ false); } }
n = nodeFactory.newNode(keyRef, newValue[0], valueReferenceQueue(), weight[1], now[0]); setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0]));
@Override public @Nullable V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction, boolean recordMiss, boolean recordLoad) { requireNonNull(key); requireNonNull(remappingFunction); long[] now = { expirationTicker().read() }; Object keyRef = nodeFactory.newReferenceKey(key, keyReferenceQueue()); BiFunction<? super K, ? super V, ? extends V> statsAwareRemappingFunction = statsAware(remappingFunction, recordMiss, recordLoad); return remap(key, keyRef, statsAwareRemappingFunction, now, /* computeIfAbsent */ true); }
@Override public @Nullable V getIfPresentQuietly(Object key, long[/* 1 */] writeTime) { V value; Node<K, V> node = data.get(nodeFactory.newLookupKey(key)); if ((node == null) || ((value = node.getValue()) == null) || hasExpired(node, expirationTicker().read())) { return null; } writeTime[0] = node.getWriteTime(); return value; }
n = nodeFactory.newNode(key, keyReferenceQueue(), newValue[0], valueReferenceQueue(), weight[1], now[0]); setVariableTime(n, expireAfterCreate(key, newValue[0], expiry(), now[0]));