* catcache.c
* System catalog cache for tuples matching a key.
*
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/cache/catcache.c,v 1.96 2002/06/20 20:29:39 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/catcache.c,v 1.121 2005/05/06 17:24:54 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
-#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "miscadmin.h"
#ifdef CATCACHE_STATS
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/catcache.h"
+#include "utils/memutils.h"
#include "utils/relcache.h"
+#include "utils/resowner.h"
#include "utils/syscache.h"
/* Cache management header --- pointer is NULL until created */
static CatCacheHeader *CacheHdr = NULL;
-/*
- * EQPROC is used in CatalogCacheInitializeCache to find the equality
- * functions for system types that are used as cache key fields.
- * See also GetCCHashFunc, which should support the same set of types.
- *
- * XXX this should be replaced by catalog lookups,
- * but that seems to pose considerable risk of circularity...
- */
-static const Oid eqproc[] = {
- F_BOOLEQ, InvalidOid, F_CHAREQ, F_NAMEEQ, InvalidOid,
- F_INT2EQ, F_INT2VECTOREQ, F_INT4EQ, F_OIDEQ, F_TEXTEQ,
- F_OIDEQ, InvalidOid, InvalidOid, InvalidOid, F_OIDVECTOREQ
-};
-
-#define EQPROC(SYSTEMTYPEOID) eqproc[(SYSTEMTYPEOID)-BOOLOID]
-
static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys,
ScanKey cur_skey);
static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache,
HeapTuple tuple);
+
#ifdef CATCACHE_STATS
static void CatCachePrintStats(void);
#endif
static void CatCacheRemoveCList(CatCache *cache, CatCList *cl);
static void CatalogCacheInitializeCache(CatCache *cache);
static CatCTup *CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
- uint32 hashValue, Index hashIndex,
- bool negative);
+ uint32 hashValue, Index hashIndex,
+ bool negative);
static HeapTuple build_dummy_tuple(CatCache *cache, int nkeys, ScanKey skeys);
* internal support functions
*/
-static PGFunction
-GetCCHashFunc(Oid keytype)
+/*
+ * Look up the hash and equality functions for system types that are used
+ * as cache key fields.
+ *
+ * XXX this should be replaced by catalog lookups,
+ * but that seems to pose considerable risk of circularity...
+ */
+static void
+GetCCHashEqFuncs(Oid keytype, PGFunction *hashfunc, RegProcedure *eqfunc)
{
switch (keytype)
{
case BOOLOID:
+ *hashfunc = hashchar;
+ *eqfunc = F_BOOLEQ;
+ break;
case CHAROID:
- return hashchar;
+ *hashfunc = hashchar;
+ *eqfunc = F_CHAREQ;
+ break;
case NAMEOID:
- return hashname;
+ *hashfunc = hashname;
+ *eqfunc = F_NAMEEQ;
+ break;
case INT2OID:
- return hashint2;
+ *hashfunc = hashint2;
+ *eqfunc = F_INT2EQ;
+ break;
case INT2VECTOROID:
- return hashint2vector;
+ *hashfunc = hashint2vector;
+ *eqfunc = F_INT2VECTOREQ;
+ break;
case INT4OID:
- return hashint4;
+ *hashfunc = hashint4;
+ *eqfunc = F_INT4EQ;
+ break;
case TEXTOID:
- return hashvarlena;
+ *hashfunc = hashtext;
+ *eqfunc = F_TEXTEQ;
+ break;
case OIDOID:
case REGPROCOID:
case REGPROCEDUREOID:
case REGOPERATOROID:
case REGCLASSOID:
case REGTYPEOID:
- return hashoid;
+ *hashfunc = hashoid;
+ *eqfunc = F_OIDEQ;
+ break;
case OIDVECTOROID:
- return hashoidvector;
+ *hashfunc = hashoidvector;
+ *eqfunc = F_OIDVECTOREQ;
+ break;
default:
- elog(FATAL, "GetCCHashFunc: type %u unsupported as catcache key",
- keytype);
- return (PGFunction) NULL;
+ elog(FATAL, "type %u not supported as catcache key", keytype);
+ break;
}
}
{
uint32 hashValue = 0;
- CACHE4_elog(DEBUG1, "CatalogCacheComputeHashValue %s %d %p",
+ CACHE4_elog(DEBUG2, "CatalogCacheComputeHashValue %s %d %p",
cache->cc_relname,
nkeys,
cache);
cur_skey[0].sk_argument));
break;
default:
- elog(FATAL, "CCComputeHashValue: %d nkeys", nkeys);
+ elog(FATAL, "wrong number of hash keys: %d", nkeys);
break;
}
case 4:
cur_skey[3].sk_argument =
(cache->cc_key[3] == ObjectIdAttributeNumber)
- ? ObjectIdGetDatum(tuple->t_data->t_oid)
+ ? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
cache->cc_key[3],
cache->cc_tupdesc,
case 3:
cur_skey[2].sk_argument =
(cache->cc_key[2] == ObjectIdAttributeNumber)
- ? ObjectIdGetDatum(tuple->t_data->t_oid)
+ ? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
cache->cc_key[2],
cache->cc_tupdesc,
case 2:
cur_skey[1].sk_argument =
(cache->cc_key[1] == ObjectIdAttributeNumber)
- ? ObjectIdGetDatum(tuple->t_data->t_oid)
+ ? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
cache->cc_key[1],
cache->cc_tupdesc,
case 1:
cur_skey[0].sk_argument =
(cache->cc_key[0] == ObjectIdAttributeNumber)
- ? ObjectIdGetDatum(tuple->t_data->t_oid)
+ ? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
cache->cc_key[0],
cache->cc_tupdesc,
Assert(!isNull);
break;
default:
- elog(FATAL, "CCComputeTupleHashValue: %d cc_nkeys",
- cache->cc_nkeys);
+ elog(FATAL, "wrong number of hash keys: %d", cache->cc_nkeys);
break;
}
long cc_lsearches = 0;
long cc_lhits = 0;
- elog(DEBUG1, "Catcache stats dump: %d/%d tuples in catcaches",
+ elog(DEBUG2, "catcache stats dump: %d/%d tuples in catcaches",
CacheHdr->ch_ntup, CacheHdr->ch_maxtup);
for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next)
{
if (cache->cc_ntup == 0 && cache->cc_searches == 0)
continue; /* don't print unused caches */
- elog(DEBUG1, "Catcache %s/%s: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
+ elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
cache->cc_relname,
- cache->cc_indname,
+ cache->cc_indexoid,
cache->cc_ntup,
cache->cc_searches,
cache->cc_hits,
cc_lsearches += cache->cc_lsearches;
cc_lhits += cache->cc_lhits;
}
- elog(DEBUG1, "Catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
+ elog(DEBUG2, "catcache totals: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld discards, %ld lsrch, %ld lhits",
CacheHdr->ch_ntup,
cc_searches,
cc_hits,
cc_lsearches,
cc_lhits);
}
-
-#endif /* CATCACHE_STATS */
+#endif /* CATCACHE_STATS */
/*
Assert(cl->my_cache == cache);
/* delink from member tuples */
- for (i = cl->n_members; --i >= 0; )
+ for (i = cl->n_members; --i >= 0;)
{
CatCTup *ct = cl->members[i];
* 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
+ * 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
+ * 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.
* sanity checks
*/
Assert(ItemPointerIsValid(pointer));
- CACHE1_elog(DEBUG1, "CatalogCacheIdInvalidate: called");
+ CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: called");
/*
* inspect caches to find the proper cache
/*
* 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.
+ * initialization yet; if not, there will be no entries in it so
+ * no problem.
*/
/*
ct->dead = true;
else
CatCacheRemoveCTup(ccp, ct);
- CACHE1_elog(DEBUG1, "CatalogCacheIdInvalidate: invalidated");
+ CACHE1_elog(DEBUG2, "CatalogCacheIdInvalidate: invalidated");
#ifdef CATCACHE_STATS
ccp->cc_invals++;
#endif
/*
* AtEOXact_CatCache
*
- * Clean up catcaches at end of transaction (either commit or abort)
+ * Clean up catcaches at end of main 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.
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);
+ PrintCatCacheListLeakWarning(cl);
cl->refcount = 0;
}
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,
- ct->tuple.t_data->t_oid,
- ct->refcount);
+ PrintCatCacheLeakWarning(&ct->tuple);
ct->refcount = 0;
}
{
CatCache *cache;
- CACHE1_elog(DEBUG1, "ResetCatalogCaches called");
+ CACHE1_elog(DEBUG2, "ResetCatalogCaches called");
for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next)
ResetCatalogCache(cache);
- CACHE1_elog(DEBUG1, "end of ResetCatalogCaches call");
+ CACHE1_elog(DEBUG2, "end of ResetCatalogCaches call");
}
/*
{
CatCache *cache;
- CACHE2_elog(DEBUG1, "CatalogCacheFlushRelation called for %u", relId);
+ CACHE2_elog(DEBUG2, "CatalogCacheFlushRelation called for %u", relId);
for (cache = CacheHdr->ch_caches; cache; cache = cache->cc_next)
{
continue;
if (cache->cc_reloidattr == ObjectIdAttributeNumber)
- tupRelid = ct->tuple.t_data->t_oid;
+ tupRelid = HeapTupleGetOid(&ct->tuple);
else
{
bool isNull;
}
}
- CACHE1_elog(DEBUG1, "end of CatalogCacheFlushRelation call");
+ CACHE1_elog(DEBUG2, "end of CatalogCacheFlushRelation call");
}
/*
* structure initialized on the first access.
*/
#ifdef CACHEDEBUG
-#define InitCatCache_DEBUG1 \
+#define InitCatCache_DEBUG2 \
do { \
- elog(DEBUG1, "InitCatCache: rel=%s id=%d nkeys=%d size=%d\n", \
- cp->cc_relname, cp->id, cp->cc_nkeys, cp->cc_nbuckets); \
+ elog(DEBUG2, "InitCatCache: rel=%u ind=%u id=%d nkeys=%d size=%d", \
+ cp->cc_reloid, cp->cc_indexoid, cp->id, \
+ cp->cc_nkeys, cp->cc_nbuckets); \
} while(0)
#else
-#define InitCatCache_DEBUG1
+#define InitCatCache_DEBUG2
#endif
CatCache *
InitCatCache(int id,
- const char *relname,
- const char *indname,
+ Oid reloid,
+ Oid indexoid,
int reloidattr,
int nkeys,
const int *key)
*
* Note: we assume zeroing initializes the Dllist headers correctly
*/
- cp = (CatCache *) palloc(sizeof(CatCache) + NCCBUCKETS * sizeof(Dllist));
- MemSet((char *) cp, 0, sizeof(CatCache) + NCCBUCKETS * sizeof(Dllist));
+ cp = (CatCache *) palloc0(sizeof(CatCache) + NCCBUCKETS * sizeof(Dllist));
/*
* initialize the cache's relation information for the relation
* other internal fields. But don't open the relation yet.
*/
cp->id = id;
- cp->cc_relname = relname;
- cp->cc_indname = indname;
- cp->cc_reloid = InvalidOid; /* temporary */
+ cp->cc_relname = "(not known yet)";
+ cp->cc_reloid = reloid;
+ cp->cc_indexoid = indexoid;
cp->cc_relisshared = false; /* temporary */
cp->cc_tupdesc = (TupleDesc) NULL;
cp->cc_reloidattr = reloidattr;
* new cache is initialized as far as we can go for now. print some
* debugging information, if appropriate.
*/
- InitCatCache_DEBUG1;
+ InitCatCache_DEBUG2;
/*
* add completed cache to top of group header's list
*/
#ifdef CACHEDEBUG
#define CatalogCacheInitializeCache_DEBUG1 \
- elog(DEBUG1, "CatalogCacheInitializeCache: cache @%p %s", cache, \
- cache->cc_relname)
+ elog(DEBUG2, "CatalogCacheInitializeCache: cache @%p rel=%u", cache, \
+ cache->cc_reloid)
#define CatalogCacheInitializeCache_DEBUG2 \
do { \
if (cache->cc_key[i] > 0) { \
- elog(DEBUG1, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \
+ elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d, %u", \
i+1, cache->cc_nkeys, cache->cc_key[i], \
tupdesc->attrs[cache->cc_key[i] - 1]->atttypid); \
} else { \
- elog(DEBUG1, "CatalogCacheInitializeCache: load %d/%d w/%d", \
+ elog(DEBUG2, "CatalogCacheInitializeCache: load %d/%d w/%d", \
i+1, cache->cc_nkeys, cache->cc_key[i]); \
} \
} while(0)
* Open the relation without locking --- we only need the tupdesc,
* which we assume will never change ...
*/
- relation = heap_openr(cache->cc_relname, NoLock);
+ relation = heap_open(cache->cc_reloid, NoLock);
Assert(RelationIsValid(relation));
/*
tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
/*
- * get the relation's OID and relisshared flag, too
+ * save the relation's name and relisshared flag, too (cc_relname
+ * is used only for debugging purposes)
*/
- cache->cc_reloid = RelationGetRelid(relation);
+ cache->cc_relname = pstrdup(RelationGetRelationName(relation));
cache->cc_relisshared = RelationGetForm(relation)->relisshared;
/*
heap_close(relation, NoLock);
- CACHE3_elog(DEBUG1, "CatalogCacheInitializeCache: %s, %d keys",
+ CACHE3_elog(DEBUG2, "CatalogCacheInitializeCache: %s, %d keys",
cache->cc_relname, cache->cc_nkeys);
/*
for (i = 0; i < cache->cc_nkeys; ++i)
{
Oid keytype;
+ RegProcedure eqfunc;
CatalogCacheInitializeCache_DEBUG2;
else
{
if (cache->cc_key[i] != ObjectIdAttributeNumber)
- elog(FATAL, "CatalogCacheInit: only sys attr supported is OID");
+ elog(FATAL, "only sys attr supported in caches is OID");
keytype = OIDOID;
}
- cache->cc_hashfunc[i] = GetCCHashFunc(keytype);
+ GetCCHashEqFuncs(keytype,
+ &cache->cc_hashfunc[i],
+ &eqfunc);
cache->cc_isname[i] = (keytype == NAMEOID);
/*
- * If GetCCHashFunc liked the type, safe to index into eqproc[]
+ * Do equality-function lookup (we assume this won't need a
+ * catalog lookup for any supported type)
*/
- cache->cc_skey[i].sk_procedure = EQPROC(keytype);
-
- /* Do function lookup */
- fmgr_info_cxt(cache->cc_skey[i].sk_procedure,
+ fmgr_info_cxt(eqfunc,
&cache->cc_skey[i].sk_func,
CacheMemoryContext);
/* Initialize sk_attno suitably for HeapKeyTest() and heap scans */
cache->cc_skey[i].sk_attno = cache->cc_key[i];
- CACHE4_elog(DEBUG1, "CatalogCacheInit %s %d %p",
+ /* Fill in sk_strategy as well --- always standard equality */
+ cache->cc_skey[i].sk_strategy = BTEqualStrategyNumber;
+ cache->cc_skey[i].sk_subtype = InvalidOid;
+
+ CACHE4_elog(DEBUG2, "CatalogCacheInit %s %d %p",
cache->cc_relname,
i,
cache);
{
Relation idesc;
- idesc = index_openr(cache->cc_indname);
+ idesc = index_open(cache->cc_indexoid);
index_close(idesc);
}
}
{
/*
* Since the OIDs of indexes aren't hardwired, it's painful to
- * figure out which is which. Just force all pg_index searches
- * to be heap scans while building the relcaches.
+ * figure out which is which. Just force all pg_index searches to
+ * be heap scans while building the relcaches.
*/
if (!criticalRelcachesBuilt)
return false;
if (!criticalRelcachesBuilt)
{
/* Looking for an OID comparison function? */
- Oid lookup_oid = DatumGetObjectId(cur_skey[0].sk_argument);
+ Oid lookup_oid = DatumGetObjectId(cur_skey[0].sk_argument);
if (lookup_oid >= MIN_OIDCMP && lookup_oid <= MAX_OIDCMP)
return false;
* if necessary (on the first access to a particular cache).
*
* The result is NULL if not found, or a pointer to a HeapTuple in
- * the cache. The caller must not modify the tuple, and must call
+ * the cache. The caller must not modify the tuple, and must call
* ReleaseCatCache() when done with it.
*
* The search key values should be expressed as Datums of the key columns'
Dlelem *elt;
CatCTup *ct;
Relation relation;
- SysScanDesc scandesc;
+ SysScanDesc scandesc;
HeapTuple ntp;
/*
continue;
/*
- * we found a match in the cache: move it to the front of the global
- * LRU list. We also move it to the front of the list for its
- * hashbucket, in order to speed subsequent searches. (The most
- * frequently accessed elements in any hashbucket will tend to be
- * near the front of the hashbucket's list.)
+ * we found a match in the cache: move it to the front of the
+ * global LRU list. We also move it to the front of the list for
+ * its hashbucket, in order to speed subsequent searches. (The
+ * most frequently accessed elements in any hashbucket will tend
+ * to be near the front of the hashbucket's list.)
*/
DLMoveToFront(&ct->lrulist_elem);
DLMoveToFront(&ct->cache_elem);
/*
- * If it's a positive entry, bump its refcount and return it.
- * If it's negative, we can report failure to the caller.
+ * If it's a positive entry, bump its refcount and return it. If
+ * it's negative, we can report failure to the caller.
*/
if (!ct->negative)
{
+ ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
ct->refcount++;
+ ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): found in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
}
else
{
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): found neg entry in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
/*
* Tuple was not found in cache, so we have to try to retrieve it
- * directly from the relation. If found, we will add it to the
- * cache; if not found, we will add a negative cache entry instead.
+ * directly from the relation. If found, we will add it to the cache;
+ * if not found, we will add a negative cache entry instead.
*
* NOTE: it is possible for recursive cache lookups to occur while
* reading the relation --- for example, due to shared-cache-inval
relation = heap_open(cache->cc_reloid, AccessShareLock);
scandesc = systable_beginscan(relation,
- cache->cc_indname,
+ cache->cc_indexoid,
IndexScanOK(cache, cur_skey),
SnapshotNow,
cache->cc_nkeys,
ct = CatalogCacheCreateEntry(cache, ntp,
hashValue, hashIndex,
false);
+ /* immediately set the refcount to 1 */
+ ResourceOwnerEnlargeCatCacheRefs(CurrentResourceOwner);
+ ct->refcount++;
+ ResourceOwnerRememberCatCacheRef(CurrentResourceOwner, &ct->tuple);
break; /* assume only one match */
}
/*
* If tuple was not found, we need to build a negative cache entry
- * containing a fake tuple. The fake tuple has the correct key columns,
- * but nulls everywhere else.
+ * containing a fake tuple. The fake tuple has the correct key
+ * columns, but nulls everywhere else.
+ *
+ * In bootstrap mode, we don't build negative entries, because the
+ * cache invalidation mechanism isn't alive and can't clear them
+ * if the tuple gets created later. (Bootstrap doesn't do UPDATEs,
+ * so it doesn't need cache inval for that.)
*/
if (ct == NULL)
{
+ if (IsBootstrapProcessingMode())
+ return NULL;
+
ntp = build_dummy_tuple(cache, cache->cc_nkeys, cur_skey);
ct = CatalogCacheCreateEntry(cache, ntp,
hashValue, hashIndex,
true);
heap_freetuple(ntp);
- CACHE4_elog(DEBUG1, "SearchCatCache(%s): Contains %d/%d tuples",
+ CACHE4_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples",
cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup);
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): put neg entry in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d",
cache->cc_relname, hashIndex);
/*
- * We are not returning the new entry to the caller, so reset its
- * refcount.
+ * We are not returning the negative entry to the caller, so leave
+ * its refcount zero.
*/
- ct->refcount = 0; /* negative entries never have refs */
return NULL;
}
- CACHE4_elog(DEBUG1, "SearchCatCache(%s): Contains %d/%d tuples",
+ CACHE4_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples",
cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup);
- CACHE3_elog(DEBUG1, "SearchCatCache(%s): put in bucket %d",
+ CACHE3_elog(DEBUG2, "SearchCatCache(%s): put in bucket %d",
cache->cc_relname, hashIndex);
#ifdef CATCACHE_STATS
Assert(ct->refcount > 0);
ct->refcount--;
+ ResourceOwnerForgetCatCacheRef(CurrentResourceOwner, &ct->tuple);
if (ct->refcount == 0
#ifndef CATCACHE_FORCE_RELEASE
CatCList *cl;
CatCTup *ct;
List *ctlist;
+ ListCell *ctlist_item;
int nmembers;
Relation relation;
- SysScanDesc scandesc;
+ SysScanDesc scandesc;
bool ordered;
HeapTuple ntp;
MemoryContext oldcxt;
/*
* compute a hash value of the given keys for faster search. We don't
- * presently divide the CatCList items into buckets, but this still lets
- * us skip non-matching items quickly most of the time.
+ * presently divide the CatCList items into buckets, but this still
+ * lets us skip non-matching items quickly most of the time.
*/
lHashValue = CatalogCacheComputeHashValue(cache, nkeys, cur_skey);
/*
* we found a matching list: move each of its members to the front
- * of the global LRU list. Also move the list itself to the front
- * of the cache's list-of-lists, to speed subsequent searches.
- * (We do not move the members to the fronts of their hashbucket
+ * of the global LRU list. Also move the list itself to the front
+ * of the cache's list-of-lists, to speed subsequent searches. (We
+ * do not move the members to the fronts of their hashbucket
* lists, however, since there's no point in that unless they are
- * searched for individually.) Also bump the members' refcounts.
+ * searched for individually.) Also bump the members' refcounts.
+ * (member refcounts are NOT registered separately with the
+ * resource owner.)
*/
+ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
for (i = 0; i < cl->n_members; i++)
{
cl->members[i]->refcount++;
/* Bump the list's refcount and return it */
cl->refcount++;
+ ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl);
- CACHE2_elog(DEBUG1, "SearchCatCacheList(%s): found list",
+ CACHE2_elog(DEBUG2, "SearchCatCacheList(%s): found list",
cache->cc_relname);
#ifdef CATCACHE_STATS
}
/*
- * List was not found in cache, so we have to build it by reading
- * the relation. For each matching tuple found in the relation,
- * use an existing cache entry if possible, else build a new one.
+ * List was not found in cache, so we have to build it by reading the
+ * relation. For each matching tuple found in the relation, use an
+ * existing cache entry if possible, else build a new one.
*/
relation = heap_open(cache->cc_reloid, AccessShareLock);
scandesc = systable_beginscan(relation,
- cache->cc_indname,
+ cache->cc_indexoid,
true,
SnapshotNow,
nkeys,
ct = (CatCTup *) DLE_VAL(elt);
if (ct->dead || ct->negative)
- continue; /* ignore dead and negative entries */
+ continue; /* ignore dead and negative entries */
if (ct->hash_value != hashValue)
- continue; /* quickly skip entry if wrong hash val */
+ continue; /* quickly skip entry if wrong hash val */
if (!ItemPointerEquals(&(ct->tuple.t_self), &(ntp->t_self)))
- continue; /* not same tuple */
+ continue; /* not same tuple */
/*
- * Found a match, but can't use it if it belongs to another list
- * already
+ * Found a match, but can't use it if it belongs to another
+ * list already
*/
if (ct->c_list)
continue;
- /* Found a match, so bump its refcount and move to front */
- ct->refcount++;
-
+ /* Found a match, so move it to front */
DLMoveToFront(&ct->lrulist_elem);
break;
false);
}
- ctlist = lcons(ct, ctlist);
+ /*
+ * We have to bump the member refcounts immediately to ensure they
+ * won't get dropped from the cache while loading other members.
+ * If we get an error before we finish constructing the CatCList
+ * then we will leak those reference counts. This is annoying but
+ * it has no real consequence beyond possibly generating some
+ * warning messages at the next transaction commit, so it's not
+ * worth fixing.
+ */
+ ct->refcount++;
+ ctlist = lappend(ctlist, ct);
nmembers++;
}
cl->cl_magic = CL_MAGIC;
cl->my_cache = cache;
- DLInitElem(&cl->cache_elem, (void *) cl);
- cl->refcount = 1; /* count this first reference */
+ DLInitElem(&cl->cache_elem, cl);
+ cl->refcount = 0; /* for the moment */
cl->dead = false;
cl->ordered = ordered;
cl->nkeys = nkeys;
cl->hash_value = lHashValue;
cl->n_members = nmembers;
- /* The list is backwards because we built it with lcons */
- for (i = nmembers; --i >= 0; )
+
+ Assert(nmembers == list_length(ctlist));
+ ctlist_item = list_head(ctlist);
+ for (i = 0; i < nmembers; i++)
{
- cl->members[i] = ct = (CatCTup *) lfirst(ctlist);
+ cl->members[i] = ct = (CatCTup *) lfirst(ctlist_item);
Assert(ct->c_list == NULL);
ct->c_list = cl;
/* mark list dead if any members already dead */
if (ct->dead)
cl->dead = true;
- ctlist = lnext(ctlist);
+ ctlist_item = lnext(ctlist_item);
}
DLAddHead(&cache->cc_lists, &cl->cache_elem);
- CACHE3_elog(DEBUG1, "SearchCatCacheList(%s): made list of %d members",
+ CACHE3_elog(DEBUG2, "SearchCatCacheList(%s): made list of %d members",
cache->cc_relname, nmembers);
+ /* Finally, bump the list's refcount and return it */
+ ResourceOwnerEnlargeCatCacheListRefs(CurrentResourceOwner);
+ cl->refcount++;
+ ResourceOwnerRememberCatCacheListRef(CurrentResourceOwner, cl);
+
return cl;
}
Assert(list->cl_magic == CL_MAGIC);
Assert(list->refcount > 0);
- for (i = list->n_members; --i >= 0; )
+ for (i = list->n_members; --i >= 0;)
{
CatCTup *ct = list->members[i];
}
list->refcount--;
+ ResourceOwnerForgetCatCacheListRef(CurrentResourceOwner, list);
if (list->refcount == 0
#ifndef CATCACHE_FORCE_RELEASE
/*
* CatalogCacheCreateEntry
* Create a new CatCTup entry, copying the given HeapTuple and other
- * supplied data into it. The new entry is given refcount 1.
+ * supplied data into it. The new entry initially has refcount 0.
*/
static CatCTup *
CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
MemoryContext oldcxt;
/*
- * Allocate CatCTup header in cache memory, and copy the tuple there too.
+ * Allocate CatCTup header in cache memory, and copy the tuple there
+ * too.
*/
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
ct = (CatCTup *) palloc(sizeof(CatCTup));
DLInitElem(&ct->lrulist_elem, (void *) ct);
DLInitElem(&ct->cache_elem, (void *) ct);
ct->c_list = NULL;
- ct->refcount = 1; /* count this first reference */
+ ct->refcount = 0; /* for the moment */
ct->dead = false;
ct->negative = negative;
ct->hash_value = hashValue;
/*
* If we've exceeded the desired size of the caches, try to throw away
- * the least recently used entry. NB: the newly-built entry cannot
- * get thrown away here, because it has positive refcount.
+ * the least recently used entry. NB: be careful not to throw away
+ * the newly-built entry...
*/
if (CacheHdr->ch_ntup > CacheHdr->ch_maxtup)
{
prevelt = DLGetPred(elt);
- if (oldct->refcount == 0)
+ if (oldct->refcount == 0 && oldct != ct)
{
- CACHE2_elog(DEBUG1, "CatCacheCreateEntry(%s): Overflow, LRU removal",
+ CACHE2_elog(DEBUG2, "CatCacheCreateEntry(%s): Overflow, LRU removal",
cache->cc_relname);
#ifdef CATCACHE_STATS
oldct->my_cache->cc_discards++;
for (i = 0; i < nkeys; i++)
{
- int attindex = cache->cc_key[i];
- Datum keyval = skeys[i].sk_argument;
+ int attindex = cache->cc_key[i];
+ Datum keyval = skeys[i].sk_argument;
if (attindex > 0)
{
/*
- * Here we must be careful in case the caller passed a
- * C string where a NAME is wanted: convert the given
- * argument to a correctly padded NAME. Otherwise the
- * memcpy() done in heap_formtuple could fall off the
- * end of memory.
+ * Here we must be careful in case the caller passed a C
+ * string where a NAME is wanted: convert the given argument
+ * to a correctly padded NAME. Otherwise the memcpy() done in
+ * heap_formtuple could fall off the end of memory.
*/
if (cache->cc_isname[i])
{
- Name newval = &tempNames[i];
+ Name newval = &tempNames[i];
namestrcpy(newval, DatumGetCString(keyval));
keyval = NameGetDatum(newval);
}
- values[attindex-1] = keyval;
- nulls[attindex-1] = ' ';
+ values[attindex - 1] = keyval;
+ nulls[attindex - 1] = ' ';
}
else
{
}
ntp = heap_formtuple(tupDesc, values, nulls);
- ntp->t_data->t_oid = tupOid;
+ if (tupOid != InvalidOid)
+ HeapTupleSetOid(ntp, tupOid);
pfree(values);
pfree(nulls);
void
PrepareToInvalidateCacheTuple(Relation relation,
HeapTuple tuple,
- void (*function) (int, uint32, ItemPointer, Oid))
+ void (*function) (int, uint32, ItemPointer, Oid))
{
CatCache *ccp;
Oid reloid;
- CACHE1_elog(DEBUG1, "PrepareToInvalidateCacheTuple: called");
+ CACHE1_elog(DEBUG2, "PrepareToInvalidateCacheTuple: called");
/*
* sanity checks
ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId);
}
}
+
+
+/*
+ * Subroutines for warning about reference leaks. These are exported so
+ * that resowner.c can call them.
+ */
+void
+PrintCatCacheLeakWarning(HeapTuple tuple)
+{
+ CatCTup *ct = (CatCTup *) (((char *) tuple) -
+ offsetof(CatCTup, tuple));
+
+ /* Safety check to ensure we were handed a cache entry */
+ Assert(ct->ct_magic == CT_MAGIC);
+
+ elog(WARNING, "cache reference leak: cache %s (%d), tuple %u/%u has count %d",
+ ct->my_cache->cc_relname, ct->my_cache->id,
+ ItemPointerGetBlockNumber(&(tuple->t_self)),
+ ItemPointerGetOffsetNumber(&(tuple->t_self)),
+ ct->refcount);
+}
+
+void
+PrintCatCacheListLeakWarning(CatCList *list)
+{
+ elog(WARNING, "cache reference leak: cache %s (%d), list %p has count %d",
+ list->my_cache->cc_relname, list->my_cache->id,
+ list, list->refcount);
+}