Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c2e228d

Browse files
committed
Fix RelationCacheInitializePhase2 (Phase3, in HEAD) to cope with the
possibility of shared-inval messages causing a relcache flush while it tries to fill in missing data in preloaded relcache entries. There are actually two distinct failure modes here: 1. The flush could delete the next-to-be-processed cache entry, causing the subsequent hash_seq_search calls to go off into the weeds. This is the problem reported by Michael Brown, and I believe it also accounts for bug #5074. The simplest fix is to restart the hashtable scan after we've read any new data from the catalogs. It appears that pre-8.4 branches have not suffered from this failure, because by chance there were no other catalogs sharing the same hash chains with the catalogs that RelationCacheInitializePhase2 had work to do for. However that's obviously pretty fragile, and it seems possible that derivative versions with additional system catalogs might be vulnerable, so I'm back-patching this part of the fix anyway. 2. The flush could delete the *current* cache entry, in which case the pointer to the newly-loaded data would end up being stored into an already-deleted Relation struct. As long as it was still deleted, the only consequence would be some leaked space in CacheMemoryContext. But it seems possible that the Relation struct could already have been recycled, in which case this represents a hard-to-reproduce clobber of cached data structures, with unforeseeable consequences. The fix here is to pin the entry while we work on it. In passing, also change RelationCacheInitializePhase2 to Assert that formrdesc() set up the relation's cached TupleDesc (rd_att) with the correct type OID and hasoids values. This is more appropriate than silently updating the values, because the original tupdesc might already have been copied into the catcache. However this part of the patch is not in HEAD because it fails due to some questionable recent changes in formrdesc :-(. That will be cleaned up in a subsequent patch.
1 parent d39a84a commit c2e228d

File tree

1 file changed

+54
-7
lines changed

1 file changed

+54
-7
lines changed

src/backend/utils/cache/relcache.c

+54-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.290 2009/08/30 17:18:52 tgl Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.291 2009/09/26 18:24:49 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -144,8 +144,7 @@ do { \
144144
RelIdCacheEnt *idhentry; bool found; \
145145
idhentry = (RelIdCacheEnt*)hash_search(RelationIdCache, \
146146
(void *) &(RELATION->rd_id), \
147-
HASH_ENTER, \
148-
&found); \
147+
HASH_ENTER, &found); \
149148
/* used to give notice if found -- now just keep quiet */ \
150149
idhentry->reldesc = RELATION; \
151150
} while(0)
@@ -154,7 +153,8 @@ do { \
154153
do { \
155154
RelIdCacheEnt *hentry; \
156155
hentry = (RelIdCacheEnt*)hash_search(RelationIdCache, \
157-
(void *) &(ID), HASH_FIND,NULL); \
156+
(void *) &(ID), \
157+
HASH_FIND, NULL); \
158158
if (hentry) \
159159
RELATION = hentry->reldesc; \
160160
else \
@@ -1412,7 +1412,9 @@ formrdesc(const char *relationName, bool isshared,
14121412
*
14131413
* The data we insert here is pretty incomplete/bogus, but it'll serve to
14141414
* get us launched. RelationCacheInitializePhase3() will read the real
1415-
* data from pg_class and replace what we've done here.
1415+
* data from pg_class and replace what we've done here. Note in particular
1416+
* that relowner is left as zero; this cues RelationCacheInitializePhase3
1417+
* that the real data isn't there yet.
14161418
*/
14171419
relation->rd_rel = (Form_pg_class) palloc0(CLASS_TUPLE_SIZE);
14181420

@@ -2687,17 +2689,31 @@ RelationCacheInitializePhase3(void)
26872689
* rows and replace the fake entries with them. Also, if any of the
26882690
* relcache entries have rules or triggers, load that info the hard way
26892691
* since it isn't recorded in the cache file.
2692+
*
2693+
* Whenever we access the catalogs to read data, there is a possibility
2694+
* of a shared-inval cache flush causing relcache entries to be removed.
2695+
* Since hash_seq_search only guarantees to still work after the *current*
2696+
* entry is removed, it's unsafe to continue the hashtable scan afterward.
2697+
* We handle this by restarting the scan from scratch after each access.
2698+
* This is theoretically O(N^2), but the number of entries that actually
2699+
* need to be fixed is small enough that it doesn't matter.
26902700
*/
26912701
hash_seq_init(&status, RelationIdCache);
26922702

26932703
while ((idhentry = (RelIdCacheEnt *) hash_seq_search(&status)) != NULL)
26942704
{
26952705
Relation relation = idhentry->reldesc;
2706+
bool restart = false;
2707+
2708+
/*
2709+
* Make sure *this* entry doesn't get flushed while we work with it.
2710+
*/
2711+
RelationIncrementReferenceCount(relation);
26962712

26972713
/*
26982714
* If it's a faked-up entry, read the real pg_class tuple.
26992715
*/
2700-
if (needNewCacheFile && relation->rd_isnailed)
2716+
if (relation->rd_rel->relowner == InvalidOid)
27012717
{
27022718
HeapTuple htup;
27032719
Form_pg_class relp;
@@ -2714,7 +2730,6 @@ RelationCacheInitializePhase3(void)
27142730
* Copy tuple to relation->rd_rel. (See notes in
27152731
* AllocateRelationDesc())
27162732
*/
2717-
Assert(relation->rd_rel != NULL);
27182733
memcpy((char *) relation->rd_rel, (char *) relp, CLASS_TUPLE_SIZE);
27192734

27202735
/* Update rd_options while we have the tuple */
@@ -2730,15 +2745,47 @@ RelationCacheInitializePhase3(void)
27302745
relation->rd_att->tdhasoid = relp->relhasoids;
27312746

27322747
ReleaseSysCache(htup);
2748+
2749+
/* relowner had better be OK now, else we'll loop forever */
2750+
if (relation->rd_rel->relowner == InvalidOid)
2751+
elog(ERROR, "invalid relowner in pg_class entry for \"%s\"",
2752+
RelationGetRelationName(relation));
2753+
2754+
restart = true;
27332755
}
27342756

27352757
/*
27362758
* Fix data that isn't saved in relcache cache file.
2759+
*
2760+
* relhasrules or relhastriggers could possibly be wrong or out of
2761+
* date. If we don't actually find any rules or triggers, clear the
2762+
* local copy of the flag so that we don't get into an infinite loop
2763+
* here. We don't make any attempt to fix the pg_class entry, though.
27372764
*/
27382765
if (relation->rd_rel->relhasrules && relation->rd_rules == NULL)
2766+
{
27392767
RelationBuildRuleLock(relation);
2768+
if (relation->rd_rules == NULL)
2769+
relation->rd_rel->relhasrules = false;
2770+
restart = true;
2771+
}
27402772
if (relation->rd_rel->relhastriggers && relation->trigdesc == NULL)
2773+
{
27412774
RelationBuildTriggers(relation);
2775+
if (relation->trigdesc == NULL)
2776+
relation->rd_rel->relhastriggers = false;
2777+
restart = true;
2778+
}
2779+
2780+
/* Release hold on the relation */
2781+
RelationDecrementReferenceCount(relation);
2782+
2783+
/* Now, restart the hashtable scan if needed */
2784+
if (restart)
2785+
{
2786+
hash_seq_term(&status);
2787+
hash_seq_init(&status, RelationIdCache);
2788+
}
27422789
}
27432790

27442791
/*

0 commit comments

Comments
 (0)