Skip to content

Commit 38ef2e2

Browse files
committed
Fix incorrect order of operations during sinval reset processing.
We have to be sure that we have revalidated each nailed-in-cache relcache entry before we try to use it to load data for some other relcache entry. The introduction of "mapped relations" in 9.0 broke this, because although we updated the state kept in relmapper.c early enough, we failed to propagate that information into relcache entries soon enough; in particular, we could try to fetch pg_class rows out of pg_class before we'd updated its relcache entry's rd_node.relNode value from the map. This bug accounts for Dave Gould's report of failures after "vacuum full pg_class", and I believe that there is risk for other system catalogs as well. The core part of the fix is to copy relmapper data into the relcache entries during "phase 1" in RelationCacheInvalidate(), before they'll be used in "phase 2". To try to future-proof the code against other similar bugs, I also rearranged the order in which nailed relations are visited during phase 2: now it's pg_class first, then pg_class_oid_index, then other nailed relations. This should ensure that RelationClearRelation can apply RelationReloadIndexInfo to all nailed indexes without risking use of not-yet-revalidated relcache entries. Back-patch to 9.0 where the relation mapper was introduced.
1 parent 44b6d53 commit 38ef2e2

File tree

1 file changed

+34
-23
lines changed

1 file changed

+34
-23
lines changed

src/backend/utils/cache/relcache.c

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,11 +2143,11 @@ RelationCacheInvalidateEntry(Oid relationId)
21432143
* so hash_seq_search will complete safely; (b) during the second pass we
21442144
* only hold onto pointers to nondeletable entries.
21452145
*
2146-
* The two-phase approach also makes it easy to ensure that we process
2147-
* nailed-in-cache indexes before other nondeletable items, and that we
2148-
* process pg_class_oid_index first of all. In scenarios where a nailed
2149-
* index has been given a new relfilenode, we have to detect that update
2150-
* before the nailed index is used in reloading any other relcache entry.
2146+
* The two-phase approach also makes it easy to update relfilenodes for
2147+
* mapped relations before we do anything else, and to ensure that the
2148+
* second pass processes nailed-in-cache items before other nondeletable
2149+
* items. This should ensure that system catalogs are up to date before
2150+
* we attempt to use them to reload information about other open relations.
21512151
*/
21522152
void
21532153
RelationCacheInvalidate(void)
@@ -2159,6 +2159,11 @@ RelationCacheInvalidate(void)
21592159
List *rebuildList = NIL;
21602160
ListCell *l;
21612161

2162+
/*
2163+
* Reload relation mapping data before starting to reconstruct cache.
2164+
*/
2165+
RelationMapInvalidateAll();
2166+
21622167
/* Phase 1 */
21632168
hash_seq_init(&status, RelationIdCache);
21642169

@@ -2169,7 +2174,7 @@ RelationCacheInvalidate(void)
21692174
/* Must close all smgr references to avoid leaving dangling ptrs */
21702175
RelationCloseSmgr(relation);
21712176

2172-
/* Ignore new relations, since they are never SI targets */
2177+
/* Ignore new relations, since they are never cross-backend targets */
21732178
if (relation->rd_createSubid != InvalidSubTransactionId)
21742179
continue;
21752180

@@ -2183,22 +2188,33 @@ RelationCacheInvalidate(void)
21832188
}
21842189
else
21852190
{
2191+
/*
2192+
* If it's a mapped relation, immediately update its rd_node in
2193+
* case its relfilenode changed. We must do this during phase 1
2194+
* in case the relation is consulted during rebuild of other
2195+
* relcache entries in phase 2. It's safe since consulting the
2196+
* map doesn't involve any access to relcache entries.
2197+
*/
2198+
if (RelationIsMapped(relation))
2199+
RelationInitPhysicalAddr(relation);
2200+
21862201
/*
21872202
* Add this entry to list of stuff to rebuild in second pass.
2188-
* pg_class_oid_index goes on the front of rebuildFirstList, other
2189-
* nailed indexes on the back, and everything else into
2190-
* rebuildList (in no particular order).
2203+
* pg_class goes to the front of rebuildFirstList while
2204+
* pg_class_oid_index goes to the back of rebuildFirstList, so
2205+
* they are done first and second respectively. Other nailed
2206+
* relations go to the front of rebuildList, so they'll be done
2207+
* next in no particular order; and everything else goes to the
2208+
* back of rebuildList.
21912209
*/
2192-
if (relation->rd_isnailed &&
2193-
relation->rd_rel->relkind == RELKIND_INDEX)
2194-
{
2195-
if (RelationGetRelid(relation) == ClassOidIndexId)
2196-
rebuildFirstList = lcons(relation, rebuildFirstList);
2197-
else
2198-
rebuildFirstList = lappend(rebuildFirstList, relation);
2199-
}
2200-
else
2210+
if (RelationGetRelid(relation) == RelationRelationId)
2211+
rebuildFirstList = lcons(relation, rebuildFirstList);
2212+
else if (RelationGetRelid(relation) == ClassOidIndexId)
2213+
rebuildFirstList = lappend(rebuildFirstList, relation);
2214+
else if (relation->rd_isnailed)
22012215
rebuildList = lcons(relation, rebuildList);
2216+
else
2217+
rebuildList = lappend(rebuildList, relation);
22022218
}
22032219
}
22042220

@@ -2209,11 +2225,6 @@ RelationCacheInvalidate(void)
22092225
*/
22102226
smgrcloseall();
22112227

2212-
/*
2213-
* Reload relation mapping data before starting to reconstruct cache.
2214-
*/
2215-
RelationMapInvalidateAll();
2216-
22172228
/* Phase 2: rebuild the items found to need rebuild in phase 1 */
22182229
foreach(l, rebuildFirstList)
22192230
{

0 commit comments

Comments
 (0)