@@ -154,6 +154,24 @@ bool criticalSharedRelcachesBuilt = false;
154
154
*/
155
155
static long relcacheInvalsReceived = 0L ;
156
156
157
+ /*
158
+ * in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE
159
+ * INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock.
160
+ * It critically relies on each backend absorbing those changes no later than
161
+ * next transaction start. Hence, RelationBuildDesc() loops until it finishes
162
+ * without accepting a relevant invalidation. (Most invalidation consumers
163
+ * don't do this.)
164
+ */
165
+ typedef struct inprogressent
166
+ {
167
+ Oid reloid ; /* OID of relation being built */
168
+ bool invalidated ; /* whether an invalidation arrived for it */
169
+ } InProgressEnt ;
170
+
171
+ static InProgressEnt * in_progress_list ;
172
+ static int in_progress_list_len ;
173
+ static int in_progress_list_maxlen ;
174
+
157
175
/*
158
176
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
159
177
* cleanup work. This list intentionally has limited size; if it overflows,
@@ -1042,6 +1060,7 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
1042
1060
static Relation
1043
1061
RelationBuildDesc (Oid targetRelId , bool insertIt )
1044
1062
{
1063
+ int in_progress_offset ;
1045
1064
Relation relation ;
1046
1065
Oid relid ;
1047
1066
HeapTuple pg_class_tuple ;
@@ -1069,6 +1088,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1069
1088
oldcxt = MemoryContextSwitchTo (tmpcxt );
1070
1089
#endif
1071
1090
1091
+ /* Register to catch invalidation messages */
1092
+ if (in_progress_list_len >= in_progress_list_maxlen )
1093
+ {
1094
+ int allocsize ;
1095
+
1096
+ allocsize = in_progress_list_maxlen * 2 ;
1097
+ in_progress_list = repalloc (in_progress_list ,
1098
+ allocsize * sizeof (* in_progress_list ));
1099
+ in_progress_list_maxlen = allocsize ;
1100
+ }
1101
+ in_progress_offset = in_progress_list_len ++ ;
1102
+ in_progress_list [in_progress_offset ].reloid = targetRelId ;
1103
+ retry :
1104
+ in_progress_list [in_progress_offset ].invalidated = false;
1105
+
1072
1106
/*
1073
1107
* find the tuple in pg_class corresponding to the given relation id
1074
1108
*/
@@ -1084,6 +1118,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1084
1118
MemoryContextSwitchTo (oldcxt );
1085
1119
MemoryContextDelete (tmpcxt );
1086
1120
#endif
1121
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1122
+ in_progress_list_len -- ;
1087
1123
return NULL ;
1088
1124
}
1089
1125
@@ -1251,6 +1287,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1251
1287
*/
1252
1288
heap_freetuple (pg_class_tuple );
1253
1289
1290
+ /*
1291
+ * If an invalidation arrived mid-build, start over. Between here and the
1292
+ * end of this function, don't add code that does or reasonably could read
1293
+ * system catalogs. That range must be free from invalidation processing
1294
+ * for the !insertIt case. For the insertIt case, RelationCacheInsert()
1295
+ * will enroll this relation in ordinary relcache invalidation processing,
1296
+ */
1297
+ if (in_progress_list [in_progress_offset ].invalidated )
1298
+ {
1299
+ RelationDestroyRelation (relation , false);
1300
+ goto retry ;
1301
+ }
1302
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1303
+ in_progress_list_len -- ;
1304
+
1254
1305
/*
1255
1306
* Insert newly created relation into relcache hash table, if requested.
1256
1307
*
@@ -2554,6 +2605,14 @@ RelationClearRelation(Relation relation, bool rebuild)
2554
2605
2555
2606
/* Build temporary entry, but don't link it into hashtable */
2556
2607
newrel = RelationBuildDesc (save_relid , false);
2608
+
2609
+ /*
2610
+ * Between here and the end of the swap, don't add code that does or
2611
+ * reasonably could read system catalogs. That range must be free
2612
+ * from invalidation processing. See RelationBuildDesc() manipulation
2613
+ * of in_progress_list.
2614
+ */
2615
+
2557
2616
if (newrel == NULL )
2558
2617
{
2559
2618
/*
@@ -2765,6 +2824,14 @@ RelationCacheInvalidateEntry(Oid relationId)
2765
2824
relcacheInvalsReceived ++ ;
2766
2825
RelationFlushRelation (relation );
2767
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
+ }
2768
2835
}
2769
2836
2770
2837
/*
@@ -2796,16 +2863,22 @@ RelationCacheInvalidateEntry(Oid relationId)
2796
2863
* second pass processes nailed-in-cache items before other nondeletable
2797
2864
* items. This should ensure that system catalogs are up to date before
2798
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.
2799
2871
*/
2800
2872
void
2801
- RelationCacheInvalidate (void )
2873
+ RelationCacheInvalidate (bool debug_discard )
2802
2874
{
2803
2875
HASH_SEQ_STATUS status ;
2804
2876
RelIdCacheEnt * idhentry ;
2805
2877
Relation relation ;
2806
2878
List * rebuildFirstList = NIL ;
2807
2879
List * rebuildList = NIL ;
2808
2880
ListCell * l ;
2881
+ int i ;
2809
2882
2810
2883
/*
2811
2884
* Reload relation mapping data before starting to reconstruct cache.
@@ -2892,6 +2965,11 @@ RelationCacheInvalidate(void)
2892
2965
RelationClearRelation (relation , true);
2893
2966
}
2894
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;
2895
2973
}
2896
2974
2897
2975
/*
@@ -2964,6 +3042,13 @@ AtEOXact_RelationCache(bool isCommit)
2964
3042
RelIdCacheEnt * idhentry ;
2965
3043
int i ;
2966
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
+
2967
3052
/*
2968
3053
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2969
3054
* listed in it. Otherwise fall back on a hash_seq_search scan.
@@ -3100,6 +3185,14 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
3100
3185
RelIdCacheEnt * idhentry ;
3101
3186
int i ;
3102
3187
3188
+ /*
3189
+ * Forget in_progress_list. This is relevant when we're aborting due to
3190
+ * an error during RelationBuildDesc(). We don't commit subtransactions
3191
+ * during RelationBuildDesc().
3192
+ */
3193
+ Assert (in_progress_list_len == 0 || !isCommit );
3194
+ in_progress_list_len = 0 ;
3195
+
3103
3196
/*
3104
3197
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3105
3198
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
@@ -3601,6 +3694,7 @@ void
3601
3694
RelationCacheInitialize (void )
3602
3695
{
3603
3696
HASHCTL ctl ;
3697
+ int allocsize ;
3604
3698
3605
3699
/*
3606
3700
* make sure cache memory context exists
@@ -3617,6 +3711,15 @@ RelationCacheInitialize(void)
3617
3711
RelationIdCache = hash_create ("Relcache by OID" , INITRELCACHESIZE ,
3618
3712
& ctl , HASH_ELEM | HASH_BLOBS );
3619
3713
3714
+ /*
3715
+ * reserve enough in_progress_list slots for many cases
3716
+ */
3717
+ allocsize = 4 ;
3718
+ in_progress_list =
3719
+ MemoryContextAlloc (CacheMemoryContext ,
3720
+ allocsize * sizeof (* in_progress_list ));
3721
+ in_progress_list_maxlen = allocsize ;
3722
+
3620
3723
/*
3621
3724
* relation mapper needs to be initialized too
3622
3725
*/
0 commit comments