@@ -135,6 +135,24 @@ bool criticalSharedRelcachesBuilt = false;
135
135
*/
136
136
static long relcacheInvalsReceived = 0L ;
137
137
138
+ /*
139
+ * in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE
140
+ * INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock.
141
+ * It critically relies on each backend absorbing those changes no later than
142
+ * next transaction start. Hence, RelationBuildDesc() loops until it finishes
143
+ * without accepting a relevant invalidation. (Most invalidation consumers
144
+ * don't do this.)
145
+ */
146
+ typedef struct inprogressent
147
+ {
148
+ Oid reloid ; /* OID of relation being built */
149
+ bool invalidated ; /* whether an invalidation arrived for it */
150
+ } InProgressEnt ;
151
+
152
+ static InProgressEnt * in_progress_list ;
153
+ static int in_progress_list_len ;
154
+ static int in_progress_list_maxlen ;
155
+
138
156
/*
139
157
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
140
158
* cleanup work. This list intentionally has limited size; if it overflows,
@@ -938,11 +956,27 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
938
956
static Relation
939
957
RelationBuildDesc (Oid targetRelId , bool insertIt )
940
958
{
959
+ int in_progress_offset ;
941
960
Relation relation ;
942
961
Oid relid ;
943
962
HeapTuple pg_class_tuple ;
944
963
Form_pg_class relp ;
945
964
965
+ /* Register to catch invalidation messages */
966
+ if (in_progress_list_len >= in_progress_list_maxlen )
967
+ {
968
+ int allocsize ;
969
+
970
+ allocsize = in_progress_list_maxlen * 2 ;
971
+ in_progress_list = repalloc (in_progress_list ,
972
+ allocsize * sizeof (* in_progress_list ));
973
+ in_progress_list_maxlen = allocsize ;
974
+ }
975
+ in_progress_offset = in_progress_list_len ++ ;
976
+ in_progress_list [in_progress_offset ].reloid = targetRelId ;
977
+ retry :
978
+ in_progress_list [in_progress_offset ].invalidated = false;
979
+
946
980
/*
947
981
* find the tuple in pg_class corresponding to the given relation id
948
982
*/
@@ -952,7 +986,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
952
986
* if no such tuple exists, return NULL
953
987
*/
954
988
if (!HeapTupleIsValid (pg_class_tuple ))
989
+ {
990
+ Assert (in_progress_offset + 1 == in_progress_list_len );
991
+ in_progress_list_len -- ;
955
992
return NULL ;
993
+ }
956
994
957
995
/*
958
996
* get information from the pg_class_tuple
@@ -1078,6 +1116,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1078
1116
*/
1079
1117
heap_freetuple (pg_class_tuple );
1080
1118
1119
+ /*
1120
+ * If an invalidation arrived mid-build, start over. Between here and the
1121
+ * end of this function, don't add code that does or reasonably could read
1122
+ * system catalogs. That range must be free from invalidation processing
1123
+ * for the !insertIt case. For the insertIt case, RelationCacheInsert()
1124
+ * will enroll this relation in ordinary relcache invalidation processing,
1125
+ */
1126
+ if (in_progress_list [in_progress_offset ].invalidated )
1127
+ {
1128
+ RelationDestroyRelation (relation , false);
1129
+ goto retry ;
1130
+ }
1131
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1132
+ in_progress_list_len -- ;
1133
+
1081
1134
/*
1082
1135
* Insert newly created relation into relcache hash table, if requested.
1083
1136
*
@@ -2282,6 +2335,14 @@ RelationClearRelation(Relation relation, bool rebuild)
2282
2335
2283
2336
/* Build temporary entry, but don't link it into hashtable */
2284
2337
newrel = RelationBuildDesc (save_relid , false);
2338
+
2339
+ /*
2340
+ * Between here and the end of the swap, don't add code that does or
2341
+ * reasonably could read system catalogs. That range must be free
2342
+ * from invalidation processing. See RelationBuildDesc() manipulation
2343
+ * of in_progress_list.
2344
+ */
2345
+
2285
2346
if (newrel == NULL )
2286
2347
{
2287
2348
/*
@@ -2457,6 +2518,14 @@ RelationCacheInvalidateEntry(Oid relationId)
2457
2518
relcacheInvalsReceived ++ ;
2458
2519
RelationFlushRelation (relation );
2459
2520
}
2521
+ else
2522
+ {
2523
+ int i ;
2524
+
2525
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2526
+ if (in_progress_list [i ].reloid == relationId )
2527
+ in_progress_list [i ].invalidated = true;
2528
+ }
2460
2529
}
2461
2530
2462
2531
/*
@@ -2488,16 +2557,22 @@ RelationCacheInvalidateEntry(Oid relationId)
2488
2557
* second pass processes nailed-in-cache items before other nondeletable
2489
2558
* items. This should ensure that system catalogs are up to date before
2490
2559
* we attempt to use them to reload information about other open relations.
2560
+ *
2561
+ * After those two phases of work having immediate effects, we normally
2562
+ * signal any RelationBuildDesc() on the stack to start over. However, we
2563
+ * don't do this if called as part of debug_discard_caches. Otherwise,
2564
+ * RelationBuildDesc() would become an infinite loop.
2491
2565
*/
2492
2566
void
2493
- RelationCacheInvalidate (void )
2567
+ RelationCacheInvalidate (bool debug_discard )
2494
2568
{
2495
2569
HASH_SEQ_STATUS status ;
2496
2570
RelIdCacheEnt * idhentry ;
2497
2571
Relation relation ;
2498
2572
List * rebuildFirstList = NIL ;
2499
2573
List * rebuildList = NIL ;
2500
2574
ListCell * l ;
2575
+ int i ;
2501
2576
2502
2577
/*
2503
2578
* Reload relation mapping data before starting to reconstruct cache.
@@ -2584,6 +2659,11 @@ RelationCacheInvalidate(void)
2584
2659
RelationClearRelation (relation , true);
2585
2660
}
2586
2661
list_free (rebuildList );
2662
+
2663
+ if (!debug_discard )
2664
+ /* Any RelationBuildDesc() on the stack must start over. */
2665
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2666
+ in_progress_list [i ].invalidated = true;
2587
2667
}
2588
2668
2589
2669
/*
@@ -2656,6 +2736,13 @@ AtEOXact_RelationCache(bool isCommit)
2656
2736
RelIdCacheEnt * idhentry ;
2657
2737
int i ;
2658
2738
2739
+ /*
2740
+ * Forget in_progress_list. This is relevant when we're aborting due to
2741
+ * an error during RelationBuildDesc().
2742
+ */
2743
+ Assert (in_progress_list_len == 0 || !isCommit );
2744
+ in_progress_list_len = 0 ;
2745
+
2659
2746
/*
2660
2747
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2661
2748
* listed in it. Otherwise fall back on a hash_seq_search scan.
@@ -2804,6 +2891,14 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
2804
2891
RelIdCacheEnt * idhentry ;
2805
2892
int i ;
2806
2893
2894
+ /*
2895
+ * Forget in_progress_list. This is relevant when we're aborting due to
2896
+ * an error during RelationBuildDesc(). We don't commit subtransactions
2897
+ * during RelationBuildDesc().
2898
+ */
2899
+ Assert (in_progress_list_len == 0 || !isCommit );
2900
+ in_progress_list_len = 0 ;
2901
+
2807
2902
/*
2808
2903
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2809
2904
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
@@ -3287,6 +3382,7 @@ void
3287
3382
RelationCacheInitialize (void )
3288
3383
{
3289
3384
HASHCTL ctl ;
3385
+ int allocsize ;
3290
3386
3291
3387
/*
3292
3388
* make sure cache memory context exists
@@ -3303,6 +3399,15 @@ RelationCacheInitialize(void)
3303
3399
RelationIdCache = hash_create ("Relcache by OID" , INITRELCACHESIZE ,
3304
3400
& ctl , HASH_ELEM | HASH_BLOBS );
3305
3401
3402
+ /*
3403
+ * reserve enough in_progress_list slots for many cases
3404
+ */
3405
+ allocsize = 4 ;
3406
+ in_progress_list =
3407
+ MemoryContextAlloc (CacheMemoryContext ,
3408
+ allocsize * sizeof (* in_progress_list ));
3409
+ in_progress_list_maxlen = allocsize ;
3410
+
3306
3411
/*
3307
3412
* relation mapper needs to be initialized too
3308
3413
*/
0 commit comments