diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 8143f08043..01ffea560a 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -276,7 +276,8 @@ static HTAB *OpClassCache = NULL; static void RelationCloseCleanup(Relation relation); static void RelationDestroyRelation(Relation relation, bool remember_tupdesc); static void RelationInvalidateRelation(Relation relation); -static void RelationClearRelation(Relation relation, bool rebuild); +static void RelationClearRelation(Relation relation); +static void RelationRebuildRelation(Relation relation); static void RelationReloadIndexInfo(Relation relation); static void RelationReloadNailed(Relation relation); @@ -721,7 +722,7 @@ RelationBuildTupleDesc(Relation relation) * we make a private memory context to hold the RuleLock information for * each relcache entry that has associated rules. The context is used * just for rule info, not for any other subsidiary data of the relcache - * entry, because that keeps the update logic in RelationClearRelation() + * entry, because that keeps the update logic in RelationRebuildRelation() * manageable. The other subsidiary data structures are simple enough * to be easy to free explicitly, anyway. * @@ -2083,7 +2084,7 @@ RelationIdGetRelation(Oid relationId) /* revalidate cache entry if necessary */ if (!rd->rd_isvalid) { - RelationClearRelation(rd, true); + RelationRebuildRelation(rd); /* * Normally entries need to be valid here, but before the relcache @@ -2211,7 +2212,7 @@ RelationCloseCleanup(Relation relation) if (RelationHasReferenceCountZero(relation) && relation->rd_createSubid == InvalidSubTransactionId && relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId) - RelationClearRelation(relation, false); + RelationClearRelation(relation); #endif } @@ -2226,15 +2227,8 @@ RelationCloseCleanup(Relation relation) * and/or in active use. We support full replacement of the pg_class row, * as well as updates of a few simple fields of the pg_index row. * - * We can't necessarily reread the catalog rows right away; we might be - * in a failed transaction when we receive the SI notification. If so, - * RelationClearRelation just marks the entry as invalid by setting - * rd_isvalid to false. This routine is called to fix the entry when it - * is next needed. - * * We assume that at the time we are called, we have at least AccessShareLock - * on the target index. (Note: in the calls from RelationClearRelation, - * this is legitimate because we know the rel has positive refcount.) + * on the target index. * * If the target index is an index on pg_class or pg_index, we'd better have * previously gotten at least AccessShareLock on its underlying catalog, @@ -2351,7 +2345,13 @@ RelationReloadIndexInfo(Relation relation) static void RelationReloadNailed(Relation relation) { + /* Should be called only for invalidated, nailed relations */ + Assert(!relation->rd_isvalid); Assert(relation->rd_isnailed); + /* nailed indexes are handled by RelationReloadIndexInfo() */ + Assert(relation->rd_rel->relkind == RELKIND_RELATION); + /* can only reread catalog contents in a transaction */ + Assert(IsTransactionState()); /* * Redo RelationInitPhysicalAddr in case it is a mapped relation whose @@ -2359,58 +2359,35 @@ RelationReloadNailed(Relation relation) */ RelationInitPhysicalAddr(relation); - /* flag as needing to be revalidated */ - relation->rd_isvalid = false; - /* - * Can only reread catalog contents if in a transaction. If the relation - * is currently open (not counting the nailed refcount), do so - * immediately. Otherwise we've already marked the entry as possibly - * invalid, and it'll be fixed when next opened. + * Reload a non-index entry. We can't easily do so if relcaches aren't + * yet built, but that's fine because at that stage the attributes that + * need to be current (like relfrozenxid) aren't yet accessed. To ensure + * the entry will later be revalidated, we leave it in invalid state, but + * allow use (cf. RelationIdGetRelation()). */ - if (!IsTransactionState() || relation->rd_refcnt <= 1) - return; - - if (relation->rd_rel->relkind == RELKIND_INDEX) + if (criticalRelcachesBuilt) { + HeapTuple pg_class_tuple; + Form_pg_class relp; + /* - * If it's a nailed-but-not-mapped index, then we need to re-read the - * pg_class row to see if its relfilenumber changed. + * NB: Mark the entry as valid before starting to scan, to avoid + * self-recursion when re-building pg_class. */ - RelationReloadIndexInfo(relation); - } - else - { + relation->rd_isvalid = true; + + pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), + true, false); + relp = (Form_pg_class) GETSTRUCT(pg_class_tuple); + memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE); + heap_freetuple(pg_class_tuple); + /* - * Reload a non-index entry. We can't easily do so if relcaches - * aren't yet built, but that's fine because at that stage the - * attributes that need to be current (like relfrozenxid) aren't yet - * accessed. To ensure the entry will later be revalidated, we leave - * it in invalid state, but allow use (cf. RelationIdGetRelation()). + * Again mark as valid, to protect against concurrently arriving + * invalidations. */ - if (criticalRelcachesBuilt) - { - HeapTuple pg_class_tuple; - Form_pg_class relp; - - /* - * NB: Mark the entry as valid before starting to scan, to avoid - * self-recursion when re-building pg_class. - */ - relation->rd_isvalid = true; - - pg_class_tuple = ScanPgRelation(RelationGetRelid(relation), - true, false); - relp = (Form_pg_class) GETSTRUCT(pg_class_tuple); - memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE); - heap_freetuple(pg_class_tuple); - - /* - * Again mark as valid, to protect against concurrently arriving - * invalidations. - */ - relation->rd_isvalid = true; - } + relation->rd_isvalid = true; } } @@ -2520,67 +2497,63 @@ RelationInvalidateRelation(Relation relation) } /* - * RelationClearRelation + * RelationClearRelation - physically blow away a relation cache entry * - * Physically blow away a relation cache entry, or reset it and rebuild - * it from scratch (that is, from catalog entries). The latter path is - * used when we are notified of a change to an open relation (one with - * refcount > 0). - * - * NB: when rebuilding, we'd better hold some lock on the relation, - * else the catalog data we need to read could be changing under us. - * Also, a rel to be rebuilt had better have refcnt > 0. This is because - * a sinval reset could happen while we're accessing the catalogs, and - * the rel would get blown away underneath us by RelationCacheInvalidate - * if it has zero refcnt. - * - * The "rebuild" parameter is redundant in current usage because it has - * to match the relation's refcnt status, but we keep it as a crosscheck - * that we're doing what the caller expects. + * The caller must ensure that the entry is no longer needed, i.e. its + * reference count is zero. Also, the rel or its storage must not be created + * in the current transaction (rd_createSubid and rd_firstRelfilelocatorSubid + * must not be set). */ static void -RelationClearRelation(Relation relation, bool rebuild) +RelationClearRelation(Relation relation) { - /* - * As per notes above, a rel to be rebuilt MUST have refcnt > 0; while of - * course it would be an equally bad idea to blow away one with nonzero - * refcnt, since that would leave someone somewhere with a dangling - * pointer. All callers are expected to have verified that this holds. - */ - Assert(rebuild ? - !RelationHasReferenceCountZero(relation) : - RelationHasReferenceCountZero(relation)); + Assert(RelationHasReferenceCountZero(relation)); + Assert(!relation->rd_isnailed); /* - * Make sure smgr and lower levels close the relation's files, if they - * weren't closed already. If the relation is not getting deleted, the - * next smgr access should reopen the files automatically. This ensures - * that the low-level file access state is updated after, say, a vacuum - * truncation. + * Relations created in the same transaction must never be removed, see + * RelationFlushRelation. */ - RelationCloseSmgr(relation); + Assert(relation->rd_createSubid == InvalidSubTransactionId); + Assert(relation->rd_firstRelfilelocatorSubid == InvalidSubTransactionId); + Assert(relation->rd_droppedSubid == InvalidSubTransactionId); - /* Free AM cached data, if any */ - if (relation->rd_amcache) - pfree(relation->rd_amcache); - relation->rd_amcache = NULL; + /* first mark it as invalid */ + RelationInvalidateRelation(relation); - /* - * Treat nailed-in system relations separately, they always need to be - * accessible, so we can't blow them away. - */ - if (relation->rd_isnailed) - { - RelationReloadNailed(relation); - return; - } + /* Remove it from the hash table */ + RelationCacheDelete(relation); - /* Mark it invalid until we've finished rebuild */ - relation->rd_isvalid = false; + /* And release storage */ + RelationDestroyRelation(relation, false); +} - /* See RelationForgetRelation(). */ - if (relation->rd_droppedSubid != InvalidSubTransactionId) - return; +/* + * RelationRebuildRelation - rebuild a relation cache entry in place + * + * Reset and rebuild a relation cache entry from scratch (that is, from + * catalog entries). This is used when we are notified of a change to an open + * relation (one with refcount > 0). The entry is reconstructed without + * moving the physical RelationData record, so that the refcount holder's + * pointer is still valid. + * + * NB: when rebuilding, we'd better hold some lock on the relation, else the + * catalog data we need to read could be changing under us. Also, a rel to be + * rebuilt had better have refcnt > 0. This is because a sinval reset could + * happen while we're accessing the catalogs, and the rel would get blown away + * underneath us by RelationCacheInvalidate if it has zero refcnt. + */ +static void +RelationRebuildRelation(Relation relation) +{ + Assert(!RelationHasReferenceCountZero(relation)); + /* rebuilding requires access to the catalogs */ + Assert(IsTransactionState()); + /* there is no reason to ever rebuild a dropped relation */ + Assert(relation->rd_droppedSubid == InvalidSubTransactionId); + + /* Close and mark it as invalid until we've finished the rebuild */ + RelationInvalidateRelation(relation); /* * Indexes only have a limited number of possible schema changes, and we @@ -2595,49 +2568,15 @@ RelationClearRelation(Relation relation, bool rebuild) */ if ((relation->rd_rel->relkind == RELKIND_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && - rebuild && relation->rd_indexcxt != NULL) { - if (IsTransactionState()) - RelationReloadIndexInfo(relation); + RelationReloadIndexInfo(relation); return; } - - /* - * If we're really done with the relcache entry, blow it away. But if - * someone is still using it, reconstruct the whole deal without moving - * the physical RelationData record (so that the someone's pointer is - * still valid). - */ - if (!rebuild) + /* Nailed relations are handled separately. */ + else if (relation->rd_isnailed) { - /* Remove it from the hash table */ - RelationCacheDelete(relation); - - /* And release storage */ - RelationDestroyRelation(relation, false); - } - else if (!IsTransactionState()) - { - /* - * If we're not inside a valid transaction, we can't do any catalog - * access so it's not possible to rebuild yet. Just exit, leaving - * rd_isvalid = false so that the rebuild will occur when the entry is - * next opened. - * - * Note: it's possible that we come here during subtransaction abort, - * and the reason for wanting to rebuild is that the rel is open in - * the outer transaction. In that case it might seem unsafe to not - * rebuild immediately, since whatever code has the rel already open - * will keep on using the relcache entry as-is. However, in such a - * case the outer transaction should be holding a lock that's - * sufficient to prevent any significant change in the rel's schema, - * so the existing entry contents should be good enough for its - * purposes; at worst we might be behind on statistics updates or the - * like. (See also CheckTableNotInUse() and its callers.) These same - * remarks also apply to the cases above where we exit without having - * done RelationReloadIndexInfo() yet. - */ + RelationReloadNailed(relation); return; } else @@ -2866,28 +2805,47 @@ RelationFlushRelation(Relation relation) * that the current transaction has some lock on the rel already. */ RelationIncrementReferenceCount(relation); - RelationClearRelation(relation, true); + RelationRebuildRelation(relation); RelationDecrementReferenceCount(relation); } else - { - /* - * During abort processing, the current resource owner is not - * valid and we cannot hold a refcnt. Without a valid - * transaction, RelationClearRelation() would just mark the rel as - * invalid anyway, so we can do the same directly. - */ RelationInvalidateRelation(relation); - } } else { /* * Pre-existing rels can be dropped from the relcache if not open. + * + * If the entry is in use, rebuild it if possible. If we're not + * inside a valid transaction, we can't do any catalog access so it's + * not possible to rebuild yet. Just mark it as invalid in that case, + * so that the rebuild will occur when the entry is next opened. + * + * Note: it's possible that we come here during subtransaction abort, + * and the reason for wanting to rebuild is that the rel is open in + * the outer transaction. In that case it might seem unsafe to not + * rebuild immediately, since whatever code has the rel already open + * will keep on using the relcache entry as-is. However, in such a + * case the outer transaction should be holding a lock that's + * sufficient to prevent any significant change in the rel's schema, + * so the existing entry contents should be good enough for its + * purposes; at worst we might be behind on statistics updates or the + * like. (See also CheckTableNotInUse() and its callers.) */ - bool rebuild = !RelationHasReferenceCountZero(relation); - - RelationClearRelation(relation, rebuild); + if (RelationHasReferenceCountZero(relation)) + RelationClearRelation(relation); + else if (!IsTransactionState()) + RelationInvalidateRelation(relation); + else if (relation->rd_isnailed && relation->rd_refcnt == 1) + { + /* + * A nailed relation with refcnt == 1 is unused. We cannot clear + * it, but there's also no need no need to rebuild it immediately. + */ + RelationInvalidateRelation(relation); + } + else + RelationRebuildRelation(relation); } } @@ -2913,14 +2871,15 @@ RelationForgetRelation(Oid rid) { /* * In the event of subtransaction rollback, we must not forget - * rd_*Subid. Mark the entry "dropped" so RelationClearRelation() - * invalidates it in lieu of destroying it. (If we're in a top - * transaction, we could opt to destroy the entry.) + * rd_*Subid. Mark the entry "dropped" and invalidate it, instead of + * destroying it right away. (If we're in a top transaction, we could + * opt to destroy the entry.) */ relation->rd_droppedSubid = GetCurrentSubTransactionId(); + RelationInvalidateRelation(relation); } - - RelationClearRelation(relation, false); + else + RelationClearRelation(relation); } /* @@ -3032,8 +2991,7 @@ RelationCacheInvalidate(bool debug_discard) if (RelationHasReferenceCountZero(relation)) { /* Delete this entry immediately */ - Assert(!relation->rd_isnailed); - RelationClearRelation(relation, false); + RelationClearRelation(relation); } else { @@ -3076,17 +3034,26 @@ RelationCacheInvalidate(bool debug_discard) */ smgrreleaseall(); - /* Phase 2: rebuild the items found to need rebuild in phase 1 */ + /* + * Phase 2: rebuild (or invalidate) the items found to need rebuild in + * phase 1 + */ foreach(l, rebuildFirstList) { relation = (Relation) lfirst(l); - RelationClearRelation(relation, true); + if (!IsTransactionState() || (relation->rd_isnailed && relation->rd_refcnt == 1)) + RelationInvalidateRelation(relation); + else + RelationRebuildRelation(relation); } list_free(rebuildFirstList); foreach(l, rebuildList) { relation = (Relation) lfirst(l); - RelationClearRelation(relation, true); + if (!IsTransactionState() || (relation->rd_isnailed && relation->rd_refcnt == 1)) + RelationInvalidateRelation(relation); + else + RelationRebuildRelation(relation); } list_free(rebuildList); @@ -3344,7 +3311,7 @@ AtEOXact_cleanup(Relation relation, bool isCommit) { if (RelationHasReferenceCountZero(relation)) { - RelationClearRelation(relation, false); + RelationClearRelation(relation); return; } else @@ -3454,7 +3421,7 @@ AtEOSubXact_cleanup(Relation relation, bool isCommit, relation->rd_newRelfilelocatorSubid = InvalidSubTransactionId; relation->rd_firstRelfilelocatorSubid = InvalidSubTransactionId; relation->rd_droppedSubid = InvalidSubTransactionId; - RelationClearRelation(relation, false); + RelationClearRelation(relation); return; } else