/* * Internal version of the CacheRelease function * * Unregisters the entry from the cleanup list if requested. */ static void Cache_ReleaseCached(Cache *cache, CacheEntry *entry, bool unregisterCleanup) { Assert(NULL != cache); Assert(NULL != entry); Assert(CACHE_ENTRY_CACHED == entry->state || CACHE_ENTRY_DELETED == entry->state); Cache_ComputeEntryHashcode(cache, entry); volatile CacheAnchor *anchor = SyncHTLookup(cache->syncHashtable, &entry->hashvalue); Assert(anchor != NULL); /* Acquire anchor lock to touch the entry */ SpinLockAcquire(&anchor->spinlock); Cache_LockEntry(cache, entry); uint32 pinCount = Cache_EntryDecRef(cache, entry); bool deleteEntry = false; if (pinCount == 0 && entry->state == CACHE_ENTRY_DELETED) { /* Delete the cache entry if pin-count = 0 and it is marked for deletion */ Cache_UnlinkEntry(cache, (CacheAnchor *) anchor, entry); deleteEntry = true; Cache_UpdatePerfCounter(&cache->cacheHdr->cacheStats.noDeletedEntries, -1 /* delta */); } Cache_UnlockEntry(cache, entry); SpinLockRelease(&anchor->spinlock); /* * Releasing anchor to hashtable. * Ignoring 'removed' return value, both values are valid */ SyncHTRelease(cache->syncHashtable, (void *) anchor); /* If requested, unregister entry from the cleanup list */ if (unregisterCleanup) { Cache_UnregisterCleanup(cache, entry); } if (deleteEntry) { if (NULL != cache->cleanupEntry) { PG_TRY(); { /* Call client-specific cleanup function before removing entry from cache */ cache->cleanupEntry(CACHE_ENTRY_PAYLOAD(entry)); } PG_CATCH(); { /* Grab entry lock to ensure exclusive access to it while we're touching it */ Cache_LockEntry(cache, entry); Assert(CACHE_ENTRY_DELETED == entry->state); entry->state = CACHE_ENTRY_FREE; #ifdef USE_ASSERT_CHECKING Cache_MemsetPayload(cache, entry); #endif Cache_UnlockEntry(cache, entry); /* Link entry back in the freelist */ Cache_AddToFreelist(cache, entry); PG_RE_THROW(); } PG_END_TRY(); } /* Grab entry lock to ensure exclusive access to it while we're touching it */ Cache_LockEntry(cache, entry); entry->state = CACHE_ENTRY_FREE; #ifdef USE_ASSERT_CHECKING Cache_MemsetPayload(cache, entry); #endif Cache_UnlockEntry(cache, entry); /* Link entry back in the freelist */ Cache_AddToFreelist(cache, entry); } }
/* * 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; }
/* * Look up an exact match for a cache entry * * Returns the matching cache entry if found, NULL otherwise */ CacheEntry * Cache_Lookup(Cache *cache, CacheEntry *entry) { Assert(NULL != cache); Assert(NULL != entry); Cache_TimedOperationStart(); Cache_UpdatePerfCounter(&cache->cacheHdr->cacheStats.noLookups, 1 /* delta */); /* Advance the clock for the replacement policy */ Cache_AdvanceClock(cache); Cache_ComputeEntryHashcode(cache, entry); volatile CacheAnchor *anchor = SyncHTLookup(cache->syncHashtable, &entry->hashvalue); if (NULL == anchor) { /* No matching anchor found, there can't be a matching element in the cache */ Cache_TimedOperationRecord(&cache->cacheHdr->cacheStats.timeLookups, &cache->cacheHdr->cacheStats.maxTimeLookup); return NULL; } /* Acquire anchor lock to touch the chain */ SpinLockAcquire(&anchor->spinlock); CacheEntry *crtEntry = anchor->firstEntry; while (true) { while (NULL != crtEntry && crtEntry->state == CACHE_ENTRY_DELETED) { /* Skip over deleted entries */ crtEntry = crtEntry->nextEntry; } if (NULL == crtEntry) { /* No valid entries found in the chain */ SpinLockRelease(&anchor->spinlock); Cache_TimedOperationRecord(&cache->cacheHdr->cacheStats.timeLookups, &cache->cacheHdr->cacheStats.maxTimeLookup); return NULL; } /* Found a valid entry. AddRef it and test to see if it matches */ Cache_EntryAddRef(cache, crtEntry); SpinLockRelease(&anchor->spinlock); /* Register it for cleanup in case we get an error while testing for equality */ Cache_RegisterCleanup(cache, crtEntry, true /* isCachedEntry */); Cache_UpdatePerfCounter(&cache->cacheHdr->cacheStats.noCompares, 1 /* delta */); if(cache->equivalentEntries(CACHE_ENTRY_PAYLOAD(entry), CACHE_ENTRY_PAYLOAD(crtEntry))) { /* Found the match, we're done */ Cache_TouchEntry(cache, crtEntry); Cache_UpdatePerfCounter(&cache->cacheHdr->cacheStats.noCacheHits, 1 /* delta */); break; } /* Unregister it from cleanup since it wasn't the one */ Cache_UnregisterCleanup(cache, crtEntry); SpinLockAcquire(&anchor->spinlock); Cache_EntryDecRef(cache, crtEntry); crtEntry = crtEntry->nextEntry; } /* ignoring return value, both values are valid */ SyncHTRelease(cache->syncHashtable, (void *) anchor); Cache_TimedOperationRecord(&cache->cacheHdr->cacheStats.timeLookups, &cache->cacheHdr->cacheStats.maxTimeLookup); return crtEntry; }