@@ -142,6 +142,24 @@ bool criticalSharedRelcachesBuilt = false;
142
142
*/
143
143
static long relcacheInvalsReceived = 0L ;
144
144
145
+ /*
146
+ * in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE
147
+ * INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock.
148
+ * It critically relies on each backend absorbing those changes no later than
149
+ * next transaction start. Hence, RelationBuildDesc() loops until it finishes
150
+ * without accepting a relevant invalidation. (Most invalidation consumers
151
+ * don't do this.)
152
+ */
153
+ typedef struct inprogressent
154
+ {
155
+ Oid reloid ; /* OID of relation being built */
156
+ bool invalidated ; /* whether an invalidation arrived for it */
157
+ } InProgressEnt ;
158
+
159
+ static InProgressEnt * in_progress_list ;
160
+ static int in_progress_list_len ;
161
+ static int in_progress_list_maxlen ;
162
+
145
163
/*
146
164
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
147
165
* cleanup work. This list intentionally has limited size; if it overflows,
@@ -1197,11 +1215,27 @@ equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
1197
1215
static Relation
1198
1216
RelationBuildDesc (Oid targetRelId , bool insertIt )
1199
1217
{
1218
+ int in_progress_offset ;
1200
1219
Relation relation ;
1201
1220
Oid relid ;
1202
1221
HeapTuple pg_class_tuple ;
1203
1222
Form_pg_class relp ;
1204
1223
1224
+ /* Register to catch invalidation messages */
1225
+ if (in_progress_list_len >= in_progress_list_maxlen )
1226
+ {
1227
+ int allocsize ;
1228
+
1229
+ allocsize = in_progress_list_maxlen * 2 ;
1230
+ in_progress_list = repalloc (in_progress_list ,
1231
+ allocsize * sizeof (* in_progress_list ));
1232
+ in_progress_list_maxlen = allocsize ;
1233
+ }
1234
+ in_progress_offset = in_progress_list_len ++ ;
1235
+ in_progress_list [in_progress_offset ].reloid = targetRelId ;
1236
+ retry :
1237
+ in_progress_list [in_progress_offset ].invalidated = false;
1238
+
1205
1239
/*
1206
1240
* find the tuple in pg_class corresponding to the given relation id
1207
1241
*/
@@ -1211,7 +1245,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1211
1245
* if no such tuple exists, return NULL
1212
1246
*/
1213
1247
if (!HeapTupleIsValid (pg_class_tuple ))
1248
+ {
1249
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1250
+ in_progress_list_len -- ;
1214
1251
return NULL ;
1252
+ }
1215
1253
1216
1254
/*
1217
1255
* get information from the pg_class_tuple
@@ -1355,6 +1393,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1355
1393
*/
1356
1394
heap_freetuple (pg_class_tuple );
1357
1395
1396
+ /*
1397
+ * If an invalidation arrived mid-build, start over. Between here and the
1398
+ * end of this function, don't add code that does or reasonably could read
1399
+ * system catalogs. That range must be free from invalidation processing
1400
+ * for the !insertIt case. For the insertIt case, RelationCacheInsert()
1401
+ * will enroll this relation in ordinary relcache invalidation processing,
1402
+ */
1403
+ if (in_progress_list [in_progress_offset ].invalidated )
1404
+ {
1405
+ RelationDestroyRelation (relation , false);
1406
+ goto retry ;
1407
+ }
1408
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1409
+ in_progress_list_len -- ;
1410
+
1358
1411
/*
1359
1412
* Insert newly created relation into relcache hash table, if requested.
1360
1413
*
@@ -2572,6 +2625,14 @@ RelationClearRelation(Relation relation, bool rebuild)
2572
2625
2573
2626
/* Build temporary entry, but don't link it into hashtable */
2574
2627
newrel = RelationBuildDesc (save_relid , false);
2628
+
2629
+ /*
2630
+ * Between here and the end of the swap, don't add code that does or
2631
+ * reasonably could read system catalogs. That range must be free
2632
+ * from invalidation processing. See RelationBuildDesc() manipulation
2633
+ * of in_progress_list.
2634
+ */
2635
+
2575
2636
if (newrel == NULL )
2576
2637
{
2577
2638
/*
@@ -2763,6 +2824,14 @@ RelationCacheInvalidateEntry(Oid relationId)
2763
2824
relcacheInvalsReceived ++ ;
2764
2825
RelationFlushRelation (relation );
2765
2826
}
2827
+ else
2828
+ {
2829
+ int i ;
2830
+
2831
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2832
+ if (in_progress_list [i ].reloid == relationId )
2833
+ in_progress_list [i ].invalidated = true;
2834
+ }
2766
2835
}
2767
2836
2768
2837
/*
@@ -2794,16 +2863,22 @@ RelationCacheInvalidateEntry(Oid relationId)
2794
2863
* second pass processes nailed-in-cache items before other nondeletable
2795
2864
* items. This should ensure that system catalogs are up to date before
2796
2865
* we attempt to use them to reload information about other open relations.
2866
+ *
2867
+ * After those two phases of work having immediate effects, we normally
2868
+ * signal any RelationBuildDesc() on the stack to start over. However, we
2869
+ * don't do this if called as part of debug_discard_caches. Otherwise,
2870
+ * RelationBuildDesc() would become an infinite loop.
2797
2871
*/
2798
2872
void
2799
- RelationCacheInvalidate (void )
2873
+ RelationCacheInvalidate (bool debug_discard )
2800
2874
{
2801
2875
HASH_SEQ_STATUS status ;
2802
2876
RelIdCacheEnt * idhentry ;
2803
2877
Relation relation ;
2804
2878
List * rebuildFirstList = NIL ;
2805
2879
List * rebuildList = NIL ;
2806
2880
ListCell * l ;
2881
+ int i ;
2807
2882
2808
2883
/*
2809
2884
* Reload relation mapping data before starting to reconstruct cache.
@@ -2890,6 +2965,11 @@ RelationCacheInvalidate(void)
2890
2965
RelationClearRelation (relation , true);
2891
2966
}
2892
2967
list_free (rebuildList );
2968
+
2969
+ if (!debug_discard )
2970
+ /* Any RelationBuildDesc() on the stack must start over. */
2971
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2972
+ in_progress_list [i ].invalidated = true;
2893
2973
}
2894
2974
2895
2975
/*
@@ -2962,6 +3042,13 @@ AtEOXact_RelationCache(bool isCommit)
2962
3042
RelIdCacheEnt * idhentry ;
2963
3043
int i ;
2964
3044
3045
+ /*
3046
+ * Forget in_progress_list. This is relevant when we're aborting due to
3047
+ * an error during RelationBuildDesc().
3048
+ */
3049
+ Assert (in_progress_list_len == 0 || !isCommit );
3050
+ in_progress_list_len = 0 ;
3051
+
2965
3052
/*
2966
3053
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2967
3054
* listed in it. Otherwise fall back on a hash_seq_search scan.
@@ -3111,6 +3198,14 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
3111
3198
RelIdCacheEnt * idhentry ;
3112
3199
int i ;
3113
3200
3201
+ /*
3202
+ * Forget in_progress_list. This is relevant when we're aborting due to
3203
+ * an error during RelationBuildDesc(). We don't commit subtransactions
3204
+ * during RelationBuildDesc().
3205
+ */
3206
+ Assert (in_progress_list_len == 0 || !isCommit );
3207
+ in_progress_list_len = 0 ;
3208
+
3114
3209
/*
3115
3210
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3116
3211
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
@@ -3597,6 +3692,7 @@ void
3597
3692
RelationCacheInitialize (void )
3598
3693
{
3599
3694
HASHCTL ctl ;
3695
+ int allocsize ;
3600
3696
3601
3697
/*
3602
3698
* make sure cache memory context exists
@@ -3613,6 +3709,15 @@ RelationCacheInitialize(void)
3613
3709
RelationIdCache = hash_create ("Relcache by OID" , INITRELCACHESIZE ,
3614
3710
& ctl , HASH_ELEM | HASH_BLOBS );
3615
3711
3712
+ /*
3713
+ * reserve enough in_progress_list slots for many cases
3714
+ */
3715
+ allocsize = 4 ;
3716
+ in_progress_list =
3717
+ MemoryContextAlloc (CacheMemoryContext ,
3718
+ allocsize * sizeof (* in_progress_list ));
3719
+ in_progress_list_maxlen = allocsize ;
3720
+
3616
3721
/*
3617
3722
* relation mapper needs to be initialized too
3618
3723
*/
0 commit comments