diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 7a12282b526242576046cc412b483a5357377ad3..2d1050b39c0017eab4adfb688830def3a86ddc0e 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.266.2.6 2009/09/26 18:25:03 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.266.2.7 2010/01/12 18:12:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -181,6 +181,7 @@ static HTAB *OpClassCache = NULL; /* non-export function prototypes */ +static void RelationDestroyRelation(Relation relation); static void RelationClearRelation(Relation relation, bool rebuild); static void RelationReloadIndexInfo(Relation relation); @@ -193,10 +194,10 @@ static void formrdesc(const char *relationName, Oid relationReltype, bool hasoids, int natts, FormData_pg_attribute *att); static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK); -static Relation AllocateRelationDesc(Relation relation, Form_pg_class relp); +static Relation AllocateRelationDesc(Form_pg_class relp); static void RelationParseRelOptions(Relation relation, HeapTuple tuple); static void RelationBuildTupleDesc(Relation relation); -static Relation RelationBuildDesc(Oid targetRelId, Relation oldrelation); +static Relation RelationBuildDesc(Oid targetRelId, bool insertIt); static void RelationInitPhysicalAddr(Relation relation); static TupleDesc GetPgClassDescriptor(void); static TupleDesc GetPgIndexDescriptor(void); @@ -275,15 +276,12 @@ ScanPgRelation(Oid targetRelId, bool indexOK) * AllocateRelationDesc * * This is used to allocate memory for a new relation descriptor - * and initialize the rd_rel field. - * - * If 'relation' is NULL, allocate a new RelationData object. - * If not, reuse the given object (that path is taken only when - * we have to rebuild a relcache entry during RelationClearRelation). + * and initialize the rd_rel field from the given pg_class tuple. */ static Relation -AllocateRelationDesc(Relation relation, Form_pg_class relp) +AllocateRelationDesc(Form_pg_class relp) { + Relation relation; MemoryContext oldcxt; Form_pg_class relationForm; @@ -291,15 +289,13 @@ AllocateRelationDesc(Relation relation, Form_pg_class relp) oldcxt = MemoryContextSwitchTo(CacheMemoryContext); /* - * allocate space for new relation descriptor, if needed + * allocate and zero space for new relation descriptor */ - if (relation == NULL) - relation = (Relation) palloc(sizeof(RelationData)); + relation = (Relation) palloc0(sizeof(RelationData)); /* - * clear all fields of reldesc + * clear fields of reldesc that should initialize to something non-zero */ - MemSet(relation, 0, sizeof(RelationData)); relation->rd_targblock = InvalidBlockNumber; /* make sure relation is marked as having no open file yet */ @@ -357,7 +353,6 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) { case RELKIND_RELATION: case RELKIND_TOASTVALUE: - case RELKIND_UNCATALOGED: case RELKIND_INDEX: break; default: @@ -381,7 +376,6 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) { case RELKIND_RELATION: case RELKIND_TOASTVALUE: - case RELKIND_UNCATALOGED: options = heap_reloptions(relation->rd_rel->relkind, datum, false); break; @@ -401,6 +395,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) relation->rd_options = MemoryContextAlloc(CacheMemoryContext, VARSIZE(options)); memcpy(relation->rd_options, options, VARSIZE(options)); + pfree(options); } } @@ -789,24 +784,22 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2) /* * RelationBuildDesc * - * Build a relation descriptor --- either a new one, or by - * recycling the given old relation object. The latter case - * supports rebuilding a relcache entry without invalidating - * pointers to it. The caller must hold at least + * Build a relation descriptor. The caller must hold at least * AccessShareLock on the target relid. * + * The new descriptor is inserted into the hash table if insertIt is true. + * * Returns NULL if no pg_class row could be found for the given relid * (suggesting we are trying to access a just-deleted relation). * Any other error is reported via elog. */ static Relation -RelationBuildDesc(Oid targetRelId, Relation oldrelation) +RelationBuildDesc(Oid targetRelId, bool insertIt) { Relation relation; Oid relid; HeapTuple pg_class_tuple; Form_pg_class relp; - MemoryContext oldcxt; /* * find the tuple in pg_class corresponding to the given relation id @@ -829,7 +822,7 @@ RelationBuildDesc(Oid targetRelId, Relation oldrelation) * allocate storage for the relation descriptor, and copy pg_class_tuple * to relation->rd_rel. */ - relation = AllocateRelationDesc(oldrelation, relp); + relation = AllocateRelationDesc(relp); /* * initialize the relation's relation id (relation->rd_id) @@ -896,11 +889,10 @@ RelationBuildDesc(Oid targetRelId, Relation oldrelation) heap_freetuple(pg_class_tuple); /* - * Insert newly created relation into relcache hash tables. + * Insert newly created relation into relcache hash table, if requested. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); - RelationCacheInsert(relation); - MemoryContextSwitchTo(oldcxt); + if (insertIt) + RelationCacheInsert(relation); /* It's fully valid */ relation->rd_isvalid = true; @@ -1531,9 +1523,19 @@ RelationIdGetRelation(Oid relationId) if (RelationIsValid(rd)) { RelationIncrementReferenceCount(rd); - /* revalidate nailed index if necessary */ + /* revalidate cache entry if necessary */ if (!rd->rd_isvalid) - RelationReloadIndexInfo(rd); + { + /* + * Indexes only have a limited number of possible schema changes, + * and we don't want to use the full-blown procedure because it's + * a headache for indexes that reload itself depends on. + */ + if (rd->rd_rel->relkind == RELKIND_INDEX) + RelationReloadIndexInfo(rd); + else + RelationClearRelation(rd, true); + } return rd; } @@ -1541,7 +1543,7 @@ RelationIdGetRelation(Oid relationId) * no reldesc in the cache, so have RelationBuildDesc() build one and add * it. */ - rd = RelationBuildDesc(relationId, NULL); + rd = RelationBuildDesc(relationId, true); if (RelationIsValid(rd)) RelationIncrementReferenceCount(rd); return rd; @@ -1703,6 +1705,50 @@ RelationReloadIndexInfo(Relation relation) relation->rd_isvalid = true; } +/* + * RelationDestroyRelation + * + * Physically delete a relation cache entry and all subsidiary data. + * Caller must already have unhooked the entry from the hash table. + */ +static void +RelationDestroyRelation(Relation relation) +{ + Assert(RelationHasReferenceCountZero(relation)); + + /* + * Make sure smgr and lower levels close the relation's files, if they + * weren't closed already. (This was probably done by caller, but let's + * just be real sure.) + */ + RelationCloseSmgr(relation); + + /* + * Free all the subsidiary data structures of the relcache entry, + * then the entry itself. + */ + if (relation->rd_rel) + pfree(relation->rd_rel); + /* can't use DecrTupleDescRefCount here */ + Assert(relation->rd_att->tdrefcount > 0); + if (--relation->rd_att->tdrefcount == 0) + FreeTupleDesc(relation->rd_att); + list_free(relation->rd_indexlist); + bms_free(relation->rd_indexattr); + FreeTriggerDesc(relation->trigdesc); + if (relation->rd_options) + pfree(relation->rd_options); + if (relation->rd_indextuple) + pfree(relation->rd_indextuple); + if (relation->rd_am) + pfree(relation->rd_am); + if (relation->rd_indexcxt) + MemoryContextDelete(relation->rd_indexcxt); + if (relation->rd_rulescxt) + MemoryContextDelete(relation->rd_rulescxt); + pfree(relation); +} + /* * RelationClearRelation * @@ -1719,7 +1765,6 @@ static void RelationClearRelation(Relation relation, bool rebuild) { Oid old_reltype = relation->rd_rel->reltype; - MemoryContext oldcxt; /* * Make sure smgr and lower levels close the relation's files, if they @@ -1771,40 +1816,14 @@ RelationClearRelation(Relation relation, bool rebuild) return; } - /* - * Remove relation from hash tables - * - * Note: we might be reinserting it momentarily, but we must not have it - * visible in the hash tables until it's valid again, so don't try to - * optimize this away... - */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); - RelationCacheDelete(relation); - MemoryContextSwitchTo(oldcxt); - - /* Clear out catcache's entries for this relation */ - CatalogCacheFlushRelation(RelationGetRelid(relation)); + /* Mark it invalid until we've finished rebuild */ + relation->rd_isvalid = false; /* - * Free all the subsidiary data structures of the relcache entry. We - * cannot free rd_att if we are trying to rebuild the entry, however, - * because pointers to it may be cached in various places. The rule - * manager might also have pointers into the rewrite rules. So to begin - * with, we can only get rid of these fields: + * Clear out catcache's entries for this relation. This is a bit of + * a hack, but it's a convenient place to do it. */ - FreeTriggerDesc(relation->trigdesc); - if (relation->rd_indextuple) - pfree(relation->rd_indextuple); - if (relation->rd_am) - pfree(relation->rd_am); - if (relation->rd_rel) - pfree(relation->rd_rel); - if (relation->rd_options) - pfree(relation->rd_options); - list_free(relation->rd_indexlist); - bms_free(relation->rd_indexattr); - if (relation->rd_indexcxt) - MemoryContextDelete(relation->rd_indexcxt); + CatalogCacheFlushRelation(RelationGetRelid(relation)); /* * If we're really done with the relcache entry, blow it away. But if @@ -1814,84 +1833,121 @@ RelationClearRelation(Relation relation, bool rebuild) */ if (!rebuild) { - /* ok to zap remaining substructure */ + /* Flush any rowtype cache entry */ flush_rowtype_cache(old_reltype); - /* can't use DecrTupleDescRefCount here */ - Assert(relation->rd_att->tdrefcount > 0); - if (--relation->rd_att->tdrefcount == 0) - FreeTupleDesc(relation->rd_att); - if (relation->rd_rulescxt) - MemoryContextDelete(relation->rd_rulescxt); - pfree(relation); + + /* Remove it from the hash table */ + RelationCacheDelete(relation); + + /* And release storage */ + RelationDestroyRelation(relation); } else { /* - * When rebuilding an open relcache entry, must preserve ref count and - * rd_createSubid/rd_newRelfilenodeSubid state. Also attempt to - * preserve the tupledesc and rewrite-rule substructures in place. - * (Note: the refcount mechanism for tupledescs may eventually ensure - * that we don't really need to preserve the tupledesc in-place, but - * for now there are still a lot of places that assume an open rel's - * tupledesc won't move.) + * Our strategy for rebuilding an open relcache entry is to build + * a new entry from scratch, swap its contents with the old entry, + * and finally delete the new entry (along with any infrastructure + * swapped over from the old entry). This is to avoid trouble in case + * an error causes us to lose control partway through. The old entry + * will still be marked !rd_isvalid, so we'll try to rebuild it again + * on next access. Meanwhile it's not any less valid than it was + * before, so any code that might expect to continue accessing it + * isn't hurt by the rebuild failure. (Consider for example a + * subtransaction that ALTERs a table and then gets cancelled partway + * through the cache entry rebuild. The outer transaction should + * still see the not-modified cache entry as valid.) The worst + * consequence of an error is leaking the necessarily-unreferenced + * new entry, and this shouldn't happen often enough for that to be + * a big problem. + * + * When rebuilding an open relcache entry, we must preserve ref count + * and rd_createSubid/rd_newRelfilenodeSubid state. Also attempt to + * preserve the pg_class entry (rd_rel), tupledesc, and rewrite-rule + * substructures in place, because various places assume that these + * structures won't move while they are working with an open relcache + * entry. (Note: the refcount mechanism for tupledescs might someday + * allow us to remove this hack for the tupledesc.) * * Note that this process does not touch CurrentResourceOwner; which * is good because whatever ref counts the entry may have do not * necessarily belong to that resource owner. */ + Relation newrel; Oid save_relid = RelationGetRelid(relation); - int old_refcnt = relation->rd_refcnt; - SubTransactionId old_createSubid = relation->rd_createSubid; - SubTransactionId old_newRelfilenodeSubid = relation->rd_newRelfilenodeSubid; - struct PgStat_TableStatus *old_pgstat_info = relation->pgstat_info; - TupleDesc old_att = relation->rd_att; - RuleLock *old_rules = relation->rd_rules; - MemoryContext old_rulescxt = relation->rd_rulescxt; - - if (RelationBuildDesc(save_relid, relation) != relation) + bool keep_tupdesc; + bool keep_rules; + + /* Build temporary entry, but don't link it into hashtable */ + newrel = RelationBuildDesc(save_relid, false); + if (newrel == NULL) { /* Should only get here if relation was deleted */ flush_rowtype_cache(old_reltype); - Assert(old_att->tdrefcount > 0); - if (--old_att->tdrefcount == 0) - FreeTupleDesc(old_att); - if (old_rulescxt) - MemoryContextDelete(old_rulescxt); - pfree(relation); + RelationCacheDelete(relation); + RelationDestroyRelation(relation); elog(ERROR, "relation %u deleted while still in use", save_relid); } - relation->rd_refcnt = old_refcnt; - relation->rd_createSubid = old_createSubid; - relation->rd_newRelfilenodeSubid = old_newRelfilenodeSubid; - relation->pgstat_info = old_pgstat_info; - if (equalTupleDescs(old_att, relation->rd_att)) - { - /* needn't flush typcache here */ - Assert(relation->rd_att->tdrefcount == 1); - if (--relation->rd_att->tdrefcount == 0) - FreeTupleDesc(relation->rd_att); - relation->rd_att = old_att; - } - else - { + keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); + keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); + if (!keep_tupdesc) flush_rowtype_cache(old_reltype); - Assert(old_att->tdrefcount > 0); - if (--old_att->tdrefcount == 0) - FreeTupleDesc(old_att); - } - if (equalRuleLocks(old_rules, relation->rd_rules)) + + /* + * Perform swapping of the relcache entry contents. Within this + * process the old entry is momentarily invalid, so there *must* + * be no possibility of CHECK_FOR_INTERRUPTS within this sequence. + * Do it in all-in-line code for safety. + * + * Since the vast majority of fields should be swapped, our method + * is to swap the whole structures and then re-swap those few fields + * we didn't want swapped. + */ +#define SWAPFIELD(fldtype, fldname) \ + do { \ + fldtype _tmp = newrel->fldname; \ + newrel->fldname = relation->fldname; \ + relation->fldname = _tmp; \ + } while (0) + + /* swap all Relation struct fields */ { - if (relation->rd_rulescxt) - MemoryContextDelete(relation->rd_rulescxt); - relation->rd_rules = old_rules; - relation->rd_rulescxt = old_rulescxt; + RelationData tmpstruct; + + memcpy(&tmpstruct, newrel, sizeof(RelationData)); + memcpy(newrel, relation, sizeof(RelationData)); + memcpy(relation, &tmpstruct, sizeof(RelationData)); } - else + + /* rd_smgr must not be swapped, due to back-links from smgr level */ + SWAPFIELD(SMgrRelation, rd_smgr); + /* rd_refcnt must be preserved */ + SWAPFIELD(int, rd_refcnt); + /* isnailed shouldn't change */ + Assert(newrel->rd_isnailed == relation->rd_isnailed); + /* creation sub-XIDs must be preserved */ + SWAPFIELD(SubTransactionId, rd_createSubid); + SWAPFIELD(SubTransactionId, rd_newRelfilenodeSubid); + /* un-swap rd_rel pointers, swap contents instead */ + SWAPFIELD(Form_pg_class, rd_rel); + /* ... but actually, we don't have to update newrel->rd_rel */ + memcpy(relation->rd_rel, newrel->rd_rel, CLASS_TUPLE_SIZE); + /* preserve old tupledesc and rules if no logical change */ + if (keep_tupdesc) + SWAPFIELD(TupleDesc, rd_att); + if (keep_rules) { - if (old_rulescxt) - MemoryContextDelete(old_rulescxt); + SWAPFIELD(RuleLock *, rd_rules); + SWAPFIELD(MemoryContext, rd_rulescxt); } + /* pgstat_info must be preserved */ + SWAPFIELD(struct PgStat_TableStatus *, pgstat_info); + +#undef SWAPFIELD + + /* And now we can throw away the temporary entry */ + RelationDestroyRelation(newrel); } } @@ -2565,7 +2621,7 @@ RelationCacheInitializePhase2(void) #define LOAD_CRIT_INDEX(indexoid) \ do { \ LockRelationOid(indexoid, AccessShareLock); \ - ird = RelationBuildDesc(indexoid, NULL); \ + ird = RelationBuildDesc(indexoid, true); \ if (ird == NULL) \ elog(PANIC, "could not open critical system index %u", \ indexoid); \ @@ -3160,7 +3216,7 @@ RelationGetIndexExpressions(Relation relation) fix_opfuncids((Node *) result); /* Now save a copy of the completed tree in the relcache entry. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); relation->rd_indexprs = (List *) copyObject(result); MemoryContextSwitchTo(oldcxt); @@ -3235,7 +3291,7 @@ RelationGetIndexPredicate(Relation relation) fix_opfuncids((Node *) result); /* Now save a copy of the completed tree in the relcache entry. */ - oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt); relation->rd_indpred = (List *) copyObject(result); MemoryContextSwitchTo(oldcxt);