static void checkValidState(LocalCache<?, ?> cchm) { for (Segment<?, ?> segment : cchm.segments) { segment.cleanUp(); assertFalse(segment.isLocked()); Map<?, ?> table = segmentTable(segment); // cleanup and then check count after we have a strong reference to all entries segment.cleanUp(); // under high memory pressure keys/values may be nulled out but not yet enqueued assertThat(table.size()).isAtMost(segment.count); for (Entry<?, ?> entry : table.entrySet()) { assertNotNull(entry.getKey()); assertNotNull(entry.getValue()); assertSame(entry.getValue(), cchm.get(entry.getKey())); } } checkEviction(cchm); checkExpiration(cchm); }
Segment<Object, Object> segment = map.segments[0]; AtomicReferenceArray<ReferenceEntry<Object, Object>> table = segment.table; assertEquals(1, table.length()); int hash = map.hash(key); DummyEntry<Object, Object> entry = createDummyEntry(key, hash, value, null); segment.recordWrite(entry, 1, map.ticker.read()); segment.table.set(0, entry); segment.readCount.incrementAndGet(); segment.count = 1; segment.totalWeight = 1; assertSame(entry, table.get(0)); assertSame(entry, segment.accessQueue.peek()); assertSame(entry, segment.writeQueue.peek()); segment.clear(); assertNull(table.get(0)); assertTrue(segment.accessQueue.isEmpty());
void expand() { AtomicReferenceArray<ReferenceEntry<K, V>> oldTable = table; int oldCapacity = oldTable.length(); if (oldCapacity >= MAXIMUM_CAPACITY) { return; AtomicReferenceArray<ReferenceEntry<K, V>> newTable = newEntryArray(oldCapacity << 1); threshold = newTable.length() * 3 / 4; int newMask = newTable.length() - 1; for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { int newIndex = e.getHash() & newMask; ReferenceEntry<K, V> newNext = newTable.get(newIndex); ReferenceEntry<K, V> newFirst = copyEntry(e, newNext); if (newFirst != null) { newTable.set(newIndex, newFirst); } else { removeCollectedEntry(e); newCount--;
/** * Returns the internal entry for the specified key. The entry may be loading, expired, or * partially collected. */ ReferenceEntry<K, V> getEntry(@Nullable Object key) { // does not impact recency ordering if (key == null) { return null; } int hash = hash(key); return segmentFor(hash).getEntry(key, hash); }
@Nullable ReferenceEntry<K, V> getLiveEntry(Object key, int hash, long now) { ReferenceEntry<K, V> e = getEntry(key, hash); if (e == null) { return null; } else if (map.isExpired(e, now)) { tryExpireEntries(now); return null; } return e; }
/** * This method is a convenience for testing. Code should call {@link Segment#newEntry} directly. */ @GuardedBy("Segment.this") @VisibleForTesting ReferenceEntry<K, V> newEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) { return segmentFor(hash).newEntry(key, hash, next); }
int hash = map.hash(one); int index = hash & (table.length() - 1); ReferenceEntry<Object, Object> entry = segment.getEntry(one, hash); ReferenceEntry<Object, Object> newEntry = segment.copyEntry(entry, null); table.set(index, newEntry); map.cleanUp(); // force notifications assertTrue(listener.isEmpty()); assertTrue(map.containsKey(one)); assertEquals(1, map.size()); assertSame(computedObject, map.get(one));
V waitForLoadingValue(ReferenceEntry<K, V> e, K key, ValueReference<K, V> valueReference) throws ExecutionException { if (!valueReference.isLoading()) { throw new AssertionError(); } 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 { statsCounter.recordMisses(1); } }
for (int i = 0; i < DRAIN_THRESHOLD * 2; i++) { Object key = new Object(); int hash = map.hash(key); Object value = new Object(); segment.recordWrite(entry, 1, map.ticker.read()); writeOrder.add(entry); readOrder.add(entry); ReferenceEntry<Object, Object> entry = i.next(); if (random.nextBoolean()) { segment.recordRead(entry, map.ticker.read()); reads.add(entry); i.remove();
public void testDrainKeyReferenceQueueOnWrite() { for (CacheBuilder<Object, Object> builder : allKeyValueStrengthMakers()) { LocalCache<Object, Object> map = makeLocalCache(builder.concurrencyLevel(1)); if (map.usesKeyReferences()) { Segment<Object, Object> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); Object valueTwo = new Object(); map.put(keyOne, valueOne); ReferenceEntry<Object, Object> entry = segment.getEntry(keyOne, hashOne); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) entry; reference.enqueue(); map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(1, map.size()); assertNull(segment.keyReferenceQueue.poll()); } } }
public void testDrainValueReferenceQueueOnWrite() { for (CacheBuilder<Object, Object> builder : allKeyValueStrengthMakers()) { LocalCache<Object, Object> map = makeLocalCache(builder.concurrencyLevel(1)); if (map.usesValueReferences()) { Segment<Object, Object> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); Object valueTwo = new Object(); map.put(keyOne, valueOne); ReferenceEntry<Object, Object> entry = segment.getEntry(keyOne, hashOne); ValueReference<Object, Object> valueReference = entry.getValueReference(); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) valueReference; reference.enqueue(); map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(1, map.size()); assertNull(segment.valueReferenceQueue.poll()); } } }
/** * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. */ // Guarded By Segment.this @VisibleForTesting ReferenceEntry<K, V> copyEntry(ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) { int hash = original.getHash(); return segmentFor(hash).copyEntry(original, newNext); }
public void testRemovalListener_collected() { QueuingRemovalListener<Object, Object> listener = queuingRemovalListener(); LocalCache<Object, Object> map = makeLocalCache( createCacheBuilder().concurrencyLevel(1).softValues().removalListener(listener)); Segment<Object, Object> segment = map.segments[0]; assertTrue(listener.isEmpty()); Object one = new Object(); Object two = new Object(); Object three = new Object(); map.put(one, two); map.put(two, three); assertTrue(listener.isEmpty()); int hash = map.hash(one); ReferenceEntry<Object, Object> entry = segment.getEntry(one, hash); map.reclaimValue(entry.getValueReference()); assertNotified(listener, one, two, RemovalCause.COLLECTED); assertTrue(listener.isEmpty()); }
map.put(key, value); ReferenceEntry<Object, Object> entry = map.getEntry(key); assertTrue(map.isLive(entry, ticker.read())); assertEquals(1, segment.writeQueue.size()); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); assertSame(value, map.get(key)); assertSame(entry, segment.writeQueue.peek()); segment.recordRead(entry, ticker.read()); segment.expireEntries(ticker.read()); assertSame(value, map.get(key)); assertSame(entry, segment.writeQueue.peek()); assertNull(map.get(key)); segment.expireEntries(ticker.read()); assertNull(map.get(key)); assertTrue(segment.writeQueue.isEmpty());
static <K, V> void checkAndDrainRecencyQueue( LocalCache<K, V> map, Segment<K, V> segment, List<ReferenceEntry<K, V>> reads) { if (map.evictsBySize() || map.expiresAfterAccess()) { assertSameEntries(reads, ImmutableList.copyOf(segment.recencyQueue)); } segment.drainRecencyQueue(); }
/** * Returns the internal entry for the specified key. The entry may be loading, expired, or * partially collected. */ ReferenceEntry<K, V> getEntry(@Nullable Object key) { // does not impact recency ordering if (key == null) { return null; } int hash = hash(key); return segmentFor(hash).getEntry(key, hash); }
/** * Updates eviction metadata that {@code entry} was just written. This currently amounts to * adding {@code entry} to relevant eviction lists. */ @GuardedBy("this") void recordWrite(ReferenceEntry<K, V> entry, int weight, long now) { // we are already under lock, so drain the recency queue immediately drainRecencyQueue(); totalWeight += weight; if (map.recordsAccess()) { entry.setAccessTime(now); } if (map.recordsWrite()) { entry.setWriteTime(now); } accessQueue.add(entry); writeQueue.add(entry); }
/** * 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 */ @GuardedBy("this") void evictEntries(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(); } } }
@GuardedBy("this") void expireEntries(long now) { drainRecencyQueue(); ReferenceEntry<K, V> e; while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) { if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { throw new AssertionError(); } } while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) { if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) { throw new AssertionError(); } } }
public void testSegmentPutIfAbsent() { LocalCache<Object, Object> map = makeLocalCache(createCacheBuilder().concurrencyLevel(1).expireAfterAccess(99999, SECONDS)); Segment<Object, Object> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); // no entry assertEquals(0, segment.count); assertNull(segment.put(key, hash, oldValue, true)); assertEquals(1, segment.count); // same key assertSame(oldValue, segment.put(key, hash, newValue, true)); assertEquals(1, segment.count); assertSame(oldValue, segment.get(key, hash)); // cleared ReferenceEntry<Object, Object> entry = segment.getEntry(key, hash); DummyValueReference<Object, Object> oldValueRef = DummyValueReference.create(oldValue); entry.setValueReference(oldValueRef); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); assertNull(segment.put(key, hash, newValue, true)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); }