/* * Sweeps through the cache and marks all entries as deleted * * Returns the number of elements it found and marked deleted. */ int32 Cache_Clear(Cache *cache) { Assert(NULL != cache); int32 startIdx = cdb_randint(cache->cacheHdr->nEntries - 1, 0); int32 entryIdx = startIdx; int32 numClearedEntries = 0; while (true) { entryIdx = (entryIdx + 1) % cache->cacheHdr->nEntries; if (entryIdx == startIdx) { /* Completed one loop through the list of all entries. We're done */ break; } CacheEntry *crtEntry = Cache_GetEntryByIndex(cache->cacheHdr, entryIdx); /* Lock entry so that nobody else changes its state until we're done with it */ Cache_LockEntry(cache, crtEntry); if (crtEntry->state != CACHE_ENTRY_CACHED) { /* Not interested in free/acquired/deleted entries. Go back and look at next entry */ Cache_UnlockEntry(cache, crtEntry); continue; } /* Found cached entry */ Cache_EntryAddRef(cache, crtEntry); if (crtEntry->state == CACHE_ENTRY_FREE || crtEntry->state == CACHE_ENTRY_ACQUIRED) { /* Someone freed up the entry before we had a chance to Add-Ref it. Skip it. */ Assert(false); Cache_EntryDecRef(cache, crtEntry); Cache_UnlockEntry(cache, crtEntry); continue; } Cache_RegisterCleanup(cache, crtEntry, true /* isCachedEntry */); Cache_Remove(cache, crtEntry); /* Done with changing the state. Unlock the entry */ Cache_UnlockEntry(cache, crtEntry); Cache_Release(cache, crtEntry); numClearedEntries++; } return numClearedEntries; }
/* * Advance cache clock by a set number of entries and decrement each entry's * utility by decAmount. * * This function doesn't do any look-ups or locking, it's supposed to be fast. */ void Cache_AdvanceClock(Cache *cache) { Assert(NULL != cache); long entriesTouched = 0; while (entriesTouched++ < cache->cacheHdr->policyContext.entriesAdvance) { bool wraparound = false; int crtIndex = Cache_NextClockHand(cache, &wraparound); CacheEntry *crtEntry = Cache_GetEntryByIndex(cache->cacheHdr, crtIndex); gp_atomic_dec_positive_32(&crtEntry->utility, cache->cacheHdr->policyContext.utilityDecrement); } }
/* * Traverses the list of cache entries and looks for the next interesting entry * Returns an entry if found, NULL if we reached the end of the loop. * * crtIndex is a pointer to the current index in the list. It is updated to * the index of the current entry while traversing. */ static CacheEntry * next_entry_to_list(Cache *cache, int32 *crtIndex) { CacheHdr *cacheHdr = cache->cacheHdr; CacheEntry *crtEntry = NULL; for ( ; (*crtIndex) < cacheHdr->nEntries ; (*crtIndex)++) { crtEntry = Cache_GetEntryByIndex(cacheHdr, *crtIndex); if (should_list_entry(crtEntry)) { (*crtIndex)++; return crtEntry; } } /* Finished the list and did not find any interesting entries */ return NULL; }
/* * Traverses the list of cache entries and looks for the next interesting entry * This is a lock-free traversal, entries might change just as we are looking * at them. The entry returned is not guaranteed to still be valid by the time * the caller looks at it. * This function should only be used for inspection purposes, for example a view * listing entries of a cache, where consistency is not an absolute requirement. * * cache: The cache we are iterating through * crtIndex: pointer to an integer holding the current index in the list. * It is updated to the index of the current entry while traversing. * * Returns an entry if found, NULL if we reached the end of the loop. */ CacheEntry * Cache_NextEntryToList(Cache *cache, int32 *crtIndex) { Assert(NULL != cache); Assert(NULL != crtIndex); Assert(*crtIndex <= cache->cacheHdr->nEntries); CacheHdr *cacheHdr = cache->cacheHdr; CacheEntry *crtEntry = NULL; for ( ; (*crtIndex) < cacheHdr->nEntries ; (*crtIndex)++) { crtEntry = Cache_GetEntryByIndex(cacheHdr, *crtIndex); if (Cache_ShouldListEntry(crtEntry)) { (*crtIndex)++; return crtEntry; } } /* Finished the list and did not find any interesting entries */ return NULL; }
/* * Run cache eviction algorithm * * It will try to evict enough entries to add up to evictSize. Returns the * actual accumulated size of the entries evicted */ int64 Cache_Evict(Cache *cache, int64 evictRequestSize) { Assert(NULL != cache); Assert(evictRequestSize > 0); Cache_TimedOperationStart(); int64 evictedSize = 0; uint32 unsuccessfulLoops = 0; bool foundVictim = false; uint32 decAmount = cache->cacheHdr->policyContext.utilityDecrement; Cache_Stats *cacheStats = &cache->cacheHdr->cacheStats; while (true) { bool wraparound = false; int32 entryIdx = Cache_NextClockHand(cache, &wraparound); Assert(entryIdx < cache->cacheHdr->nEntries); Cache_UpdatePerfCounter(&cacheStats->noEntriesScanned,1 /* delta */); if (wraparound) { unsuccessfulLoops++; Cache_UpdatePerfCounter(&cacheStats->noWraparound, 1 /* delta */); if (!foundVictim) { /* * We looped around and did not manage to evict any entries. * Double the amount we decrement eviction candidate's utility by. * This makes the eviction algorithm look for a victim more aggressively */ if (decAmount <= CACHE_MAX_UTILITY / 2) { decAmount = 2 * decAmount; } else { decAmount = CACHE_MAX_UTILITY; } } foundVictim = false; if (unsuccessfulLoops > cache->cacheHdr->policyContext.maxClockLoops) { /* Can't find any cached and unused entries candidates for evictions, even after looping around * maxClockLoops times. Give up looking for victims. */ Cache_TimedOperationRecord(&cacheStats->timeEvictions, &cacheStats->maxTimeEvict); break; } } CacheEntry *crtEntry = Cache_GetEntryByIndex(cache->cacheHdr, entryIdx); if (crtEntry->state != CACHE_ENTRY_CACHED) { /* Not interested in free/acquired/deleted entries. Go back and advance clock hand */ continue; } CacheAnchor *anchor = (CacheAnchor *) SyncHTLookup(cache->syncHashtable, &crtEntry->hashvalue); if (NULL == anchor) { /* There's no anchor for this entry, someone might have snatched it in the meantime */ continue; } SpinLockAcquire(&anchor->spinlock); if (crtEntry->state != CACHE_ENTRY_CACHED) { /* Someone freed this entry in the meantime, before we got a chance to acquire the anchor lock */ SpinLockRelease(&anchor->spinlock); SyncHTRelease(cache->syncHashtable, (void *) anchor); continue; } /* Ok, did all the checks, this entry must be valid now */ CACHE_ASSERT_VALID(crtEntry); if (crtEntry->pinCount > 0) { /* Entry is in use and can't be evicted. Go back and advance clock hand */ SpinLockRelease(&anchor->spinlock); SyncHTRelease(cache->syncHashtable, (void *) anchor); continue; } /* Decrement utility */ gp_atomic_dec_positive_32(&crtEntry->utility, decAmount); /* Just decremented someone's utility. Reset our unsuccessful loops counter */ unsuccessfulLoops = 0; if (crtEntry->utility > 0) { /* Entry has non-zero utility, we shouldn't evict it. Go back and advance clock hand */ SpinLockRelease(&anchor->spinlock); SyncHTRelease(cache->syncHashtable, (void *) anchor); continue; } /* Found our victim */ Assert(0 == crtEntry->pinCount); CACHE_ASSERT_VALID(crtEntry); Assert(crtEntry->utility == 0); #if USE_ASSERT_CHECKING int32 casResult = #endif compare_and_swap_32(&crtEntry->state, CACHE_ENTRY_CACHED, CACHE_ENTRY_DELETED); Assert(1 == casResult); SpinLockRelease(&anchor->spinlock); foundVictim = true; evictedSize += crtEntry->size; /* Don't update noFreeEntries yet. It will be done in Cache_AddToFreelist */ Cache_UpdatePerfCounter(&cacheStats->noCachedEntries, -1 /* delta */); /* Unlink entry from the anchor chain */ SpinLockAcquire(&anchor->spinlock); Cache_UnlinkEntry(cache, anchor, crtEntry); SpinLockRelease(&anchor->spinlock); SyncHTRelease(cache->syncHashtable, (void *) anchor); if (NULL != cache->cleanupEntry) { /* Call client-side cleanup for entry */ cache->cleanupEntry(CACHE_ENTRY_PAYLOAD(crtEntry)); } Cache_LockEntry(cache, crtEntry); Assert(crtEntry->state == CACHE_ENTRY_DELETED); crtEntry->state = CACHE_ENTRY_FREE; #if USE_ASSERT_CHECKING Cache_MemsetPayload(cache, crtEntry); #endif Cache_UnlockEntry(cache, crtEntry); Cache_AddToFreelist(cache, crtEntry); Cache_UpdatePerfCounter(&cacheStats->noEvicts, 1 /* delta */); Cache_TimedOperationRecord(&cacheStats->timeEvictions, &cacheStats->maxTimeEvict); if (evictedSize >= evictRequestSize) { /* We evicted as much as requested */ break; } Cache_TimedOperationStart(); } return evictedSize; }