/* * CatCacheRemoveCTup * * Unlink and delete the given cache entry * * NB: if it is a member of a CatCList, the CatCList is deleted too. * Both the cache entry and the list had better have zero refcount. */ static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) { Assert(ct->refcount == 0); Assert(ct->my_cache == cache); if (ct->c_list) { /* * The cleanest way to handle this is to call CatCacheRemoveCList, * which will recurse back to me, and the recursive call will do the * work. Set the "dead" flag to make sure it does recurse. */ ct->dead = true; CatCacheRemoveCList(cache, ct->c_list); return; /* nothing left to do */ } /* delink from linked list */ DLRemove(&ct->cache_elem); /* free associated tuple data */ if (ct->tuple.t_data != NULL) pfree(ct->tuple.t_data); pfree(ct); --cache->cc_ntup; --CacheHdr->ch_ntup; }
/* * ReleaseCatCacheList * * Decrement the reference counts of a catcache list. */ void ReleaseCatCacheList(CatCList *list) { int i; /* Safety checks to ensure we were handed a cache entry */ Assert(list->cl_magic == CL_MAGIC); Assert(list->refcount > 0); for (i = list->n_members; --i >= 0;) { CatCTup *ct = list->members[i]; Assert(ct->refcount > 0); ct->refcount--; if (ct->dead) list->dead = true; /* can't remove tuple before list is removed */ } list->refcount--; if (list->refcount == 0 #ifndef CATCACHE_FORCE_RELEASE && list->dead #endif ) CatCacheRemoveCList(list->my_cache, list); }
/* * AtEOXact_CatCache * * Clean up catcaches at end of transaction (either commit or abort) * * We scan the caches to reset refcounts to zero. This is of course * necessary in the abort case, since elog() may have interrupted routines. * In the commit case, any nonzero counts indicate failure to call * ReleaseSysCache, so we put out a notice for debugging purposes. */ void AtEOXact_CatCache(bool isCommit) { CatCache *ccp; Dlelem *elt, *nextelt; /* * First clean up CatCLists */ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) { for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt) { CatCList *cl = (CatCList *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (cl->refcount != 0) { if (isCommit) elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d", ccp->cc_relname, ccp->id, cl, cl->refcount); cl->refcount = 0; } /* Clean up any now-deletable dead entries */ if (cl->dead) CatCacheRemoveCList(ccp, cl); } } /* * Now clean up tuples; we can scan them all using the global LRU list */ for (elt = DLGetHead(&CacheHdr->ch_lrulist); elt; elt = nextelt) { CatCTup *ct = (CatCTup *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (ct->refcount != 0) { if (isCommit) elog(WARNING, "cache reference leak: cache %s (%d), tuple %u has count %d", ct->my_cache->cc_relname, ct->my_cache->id, HeapTupleGetOid(&ct->tuple), ct->refcount); ct->refcount = 0; } /* Clean up any now-deletable dead entries */ if (ct->dead) CatCacheRemoveCTup(ct->my_cache, ct); } }
/* * ResetCatalogCache * * Reset one catalog cache to empty. * * This is not very efficient if the target cache is nearly empty. * However, it shouldn't need to be efficient; we don't invoke it often. */ static void ResetCatalogCache(CatCache *cache) { Dlelem *elt, *nextelt; int i; /* Remove each list in this cache, or at least mark it dead */ for (elt = DLGetHead(&cache->cc_lists); elt; elt = nextelt) { CatCList *cl = (CatCList *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (cl->refcount > 0) cl->dead = true; else CatCacheRemoveCList(cache, cl); } /* Remove each tuple in this cache, or at least mark it dead */ for (i = 0; i < cache->cc_nbuckets; i++) { for (elt = DLGetHead(&cache->cc_bucket[i]); elt; elt = nextelt) { CatCTup *ct = (CatCTup *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (ct->refcount > 0 || (ct->c_list && ct->c_list->refcount > 0)) { ct->dead = true; /* list, if any, was marked dead above */ Assert(ct->c_list == NULL || ct->c_list->dead); } else CatCacheRemoveCTup(cache, ct); #ifdef CATCACHE_STATS cache->cc_invals++; #endif } } }
/* * CatCacheRemoveCTup * * Unlink and delete the given cache entry * * NB: if it is a member of a CatCList, the CatCList is deleted too. */ static void CatCacheRemoveCTup(CatCache *cache, CatCTup *ct) { Assert(ct->refcount == 0); Assert(ct->my_cache == cache); if (ct->c_list) CatCacheRemoveCList(cache, ct->c_list); /* delink from linked lists */ DLRemove(&ct->lrulist_elem); DLRemove(&ct->cache_elem); /* free associated tuple data */ if (ct->tuple.t_data != NULL) pfree(ct->tuple.t_data); pfree(ct); --cache->cc_ntup; --CacheHdr->ch_ntup; }
/* * CatalogCacheIdInvalidate * * Invalidate entries in the specified cache, given a hash value. * * We delete cache entries that match the hash value, whether positive * or negative. We don't care whether the invalidation is the result * of a tuple insertion or a deletion. * * We used to try to match positive cache entries by TID, but that is * unsafe after a VACUUM FULL on a system catalog: an inval event could * be queued before VACUUM FULL, and then processed afterwards, when the * target tuple that has to be invalidated has a different TID than it * did when the event was created. So now we just compare hash values and * accept the small risk of unnecessary invalidations due to false matches. * * This routine is only quasi-public: it should only be used by inval.c. */ void CatalogCacheIdInvalidate(int cacheId, uint32 hashValue) { CatCache *ccp; CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: called"); /* * inspect caches to find the proper cache */ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) { Index hashIndex; Dlelem *elt, *nextelt; if (cacheId != ccp->id) continue; /* * We don't bother to check whether the cache has finished * initialization yet; if not, there will be no entries in it so no * problem. */ /* * Invalidate *all* CatCLists in this cache; it's too hard to tell * which searches might still be correct, so just zap 'em all. */ for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt) { CatCList *cl = (CatCList *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (cl->refcount > 0) cl->dead = true; else CatCacheRemoveCList(ccp, cl); } /* * inspect the proper hash bucket for tuple matches */ hashIndex = HASH_INDEX(hashValue, ccp->cc_nbuckets); for (elt = DLGetHead(&ccp->cc_bucket[hashIndex]); elt; elt = nextelt) { CatCTup *ct = (CatCTup *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (hashValue == ct->hash_value) { if (ct->refcount > 0 || (ct->c_list && ct->c_list->refcount > 0)) { ct->dead = true; /* list, if any, was marked dead above */ Assert(ct->c_list == NULL || ct->c_list->dead); } else CatCacheRemoveCTup(ccp, ct); CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: invalidated"); #ifdef CATCACHE_STATS ccp->cc_invals++; #endif /* could be multiple matches, so keep looking! */ } } break; /* need only search this one cache */ } }
/* * CatalogCacheIdInvalidate * * Invalidate entries in the specified cache, given a hash value and * item pointer. Positive entries are deleted if they match the item * pointer. Negative entries must be deleted if they match the hash * value (since we do not have the exact key of the tuple that's being * inserted). But this should only rarely result in loss of a cache * entry that could have been kept. * * Note that it's not very relevant whether the tuple identified by * the item pointer is being inserted or deleted. We don't expect to * find matching positive entries in the one case, and we don't expect * to find matching negative entries in the other; but we will do the * right things in any case. * * This routine is only quasi-public: it should only be used by inval.c. */ void CatalogCacheIdInvalidate(int cacheId, uint32 hashValue, ItemPointer pointer) { CatCache *ccp; /* * sanity checks */ #ifdef USE_ASSERT_CHECKING /* Add some debug info for MPP-5739 */ if (!ItemPointerIsValid(pointer)) { elog(LOG, "CatalogCacheIdInvalidate: cacheId %d, hash %u IP %p", cacheId, hashValue, pointer); if (pointer != NULL) { elog(LOG, "CatalogCacheIdInvalidate: bogus item (?): (blkid.hi %d blkid.lo %d posid %d)", pointer->ip_blkid.bi_hi, pointer->ip_blkid.bi_lo, pointer->ip_posid); } } #endif Assert(ItemPointerIsValid(pointer)); CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: called"); /* * inspect caches to find the proper cache */ for (ccp = CacheHdr->ch_caches; ccp; ccp = ccp->cc_next) { Index hashIndex; Dlelem *elt, *nextelt; if (cacheId != ccp->id) continue; /* * We don't bother to check whether the cache has finished * initialization yet; if not, there will be no entries in it so no * problem. */ /* * Invalidate *all* CatCLists in this cache; it's too hard to tell * which searches might still be correct, so just zap 'em all. */ for (elt = DLGetHead(&ccp->cc_lists); elt; elt = nextelt) { CatCList *cl = (CatCList *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (cl->refcount > 0) cl->dead = true; else CatCacheRemoveCList(ccp, cl); } /* * inspect the proper hash bucket for tuple matches */ hashIndex = HASH_INDEX(hashValue, ccp->cc_nbuckets); for (elt = DLGetHead(&ccp->cc_bucket[hashIndex]); elt; elt = nextelt) { CatCTup *ct = (CatCTup *) DLE_VAL(elt); nextelt = DLGetSucc(elt); if (hashValue != ct->hash_value) continue; /* ignore non-matching hash values */ if (ct->negative || ItemPointerEquals(pointer, &ct->tuple.t_self)) { if (ct->refcount > 0 || (ct->c_list && ct->c_list->refcount > 0)) { ct->dead = true; /* list, if any, was marked dead above */ Assert(ct->c_list == NULL || ct->c_list->dead); } else CatCacheRemoveCTup(ccp, ct); CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: invalidated"); #ifdef CATCACHE_STATS ccp->cc_invals++; #endif /* could be multiple matches, so keep looking! */ } } break; /* need only search this one cache */ } }