@@ -150,6 +150,24 @@ bool criticalSharedRelcachesBuilt = false;
150
150
*/
151
151
static long relcacheInvalsReceived = 0L ;
152
152
153
+ /*
154
+ * in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE
155
+ * INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock.
156
+ * It critically relies on each backend absorbing those changes no later than
157
+ * next transaction start. Hence, RelationBuildDesc() loops until it finishes
158
+ * without accepting a relevant invalidation. (Most invalidation consumers
159
+ * don't do this.)
160
+ */
161
+ typedef struct inprogressent
162
+ {
163
+ Oid reloid ; /* OID of relation being built */
164
+ bool invalidated ; /* whether an invalidation arrived for it */
165
+ } InProgressEnt ;
166
+
167
+ static InProgressEnt * in_progress_list ;
168
+ static int in_progress_list_len ;
169
+ static int in_progress_list_maxlen ;
170
+
153
171
/*
154
172
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
155
173
* cleanup work. This list intentionally has limited size; if it overflows,
@@ -1043,6 +1061,7 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
1043
1061
static Relation
1044
1062
RelationBuildDesc (Oid targetRelId , bool insertIt )
1045
1063
{
1064
+ int in_progress_offset ;
1046
1065
Relation relation ;
1047
1066
Oid relid ;
1048
1067
HeapTuple pg_class_tuple ;
@@ -1070,6 +1089,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1070
1089
oldcxt = MemoryContextSwitchTo (tmpcxt );
1071
1090
#endif
1072
1091
1092
+ /* Register to catch invalidation messages */
1093
+ if (in_progress_list_len >= in_progress_list_maxlen )
1094
+ {
1095
+ int allocsize ;
1096
+
1097
+ allocsize = in_progress_list_maxlen * 2 ;
1098
+ in_progress_list = repalloc (in_progress_list ,
1099
+ allocsize * sizeof (* in_progress_list ));
1100
+ in_progress_list_maxlen = allocsize ;
1101
+ }
1102
+ in_progress_offset = in_progress_list_len ++ ;
1103
+ in_progress_list [in_progress_offset ].reloid = targetRelId ;
1104
+ retry :
1105
+ in_progress_list [in_progress_offset ].invalidated = false;
1106
+
1073
1107
/*
1074
1108
* find the tuple in pg_class corresponding to the given relation id
1075
1109
*/
@@ -1085,6 +1119,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1085
1119
MemoryContextSwitchTo (oldcxt );
1086
1120
MemoryContextDelete (tmpcxt );
1087
1121
#endif
1122
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1123
+ in_progress_list_len -- ;
1088
1124
return NULL ;
1089
1125
}
1090
1126
@@ -1244,6 +1280,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1244
1280
*/
1245
1281
heap_freetuple (pg_class_tuple );
1246
1282
1283
+ /*
1284
+ * If an invalidation arrived mid-build, start over. Between here and the
1285
+ * end of this function, don't add code that does or reasonably could read
1286
+ * system catalogs. That range must be free from invalidation processing
1287
+ * for the !insertIt case. For the insertIt case, RelationCacheInsert()
1288
+ * will enroll this relation in ordinary relcache invalidation processing,
1289
+ */
1290
+ if (in_progress_list [in_progress_offset ].invalidated )
1291
+ {
1292
+ RelationDestroyRelation (relation , false);
1293
+ goto retry ;
1294
+ }
1295
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1296
+ in_progress_list_len -- ;
1297
+
1247
1298
/*
1248
1299
* Insert newly created relation into relcache hash table, if requested.
1249
1300
*
@@ -2586,6 +2637,14 @@ RelationClearRelation(Relation relation, bool rebuild)
2586
2637
2587
2638
/* Build temporary entry, but don't link it into hashtable */
2588
2639
newrel = RelationBuildDesc (save_relid , false);
2640
+
2641
+ /*
2642
+ * Between here and the end of the swap, don't add code that does or
2643
+ * reasonably could read system catalogs. That range must be free
2644
+ * from invalidation processing. See RelationBuildDesc() manipulation
2645
+ * of in_progress_list.
2646
+ */
2647
+
2589
2648
if (newrel == NULL )
2590
2649
{
2591
2650
/*
@@ -2816,6 +2875,14 @@ RelationCacheInvalidateEntry(Oid relationId)
2816
2875
relcacheInvalsReceived ++ ;
2817
2876
RelationFlushRelation (relation );
2818
2877
}
2878
+ else
2879
+ {
2880
+ int i ;
2881
+
2882
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2883
+ if (in_progress_list [i ].reloid == relationId )
2884
+ in_progress_list [i ].invalidated = true;
2885
+ }
2819
2886
}
2820
2887
2821
2888
/*
@@ -2824,11 +2891,11 @@ RelationCacheInvalidateEntry(Oid relationId)
2824
2891
* and rebuild those with positive reference counts. Also reset the smgr
2825
2892
* relation cache and re-read relation mapping data.
2826
2893
*
2827
- * This is currently used only to recover from SI message buffer overflow,
2828
- * so we do not touch relations having new-in-transaction relfilenodes; they
2829
- * cannot be targets of cross-backend SI updates (and our own updates now go
2830
- * through a separate linked list that isn't limited by the SI message
2831
- * buffer size).
2894
+ * Apart from debug_discard_caches, this is currently used only to recover
2895
+ * from SI message buffer overflow, so we do not touch relations having
2896
+ * new-in-transaction relfilenodes; they cannot be targets of cross-backend
2897
+ * SI updates (and our own updates now go through a separate linked list
2898
+ * that isn't limited by the SI message buffer size).
2832
2899
*
2833
2900
* We do this in two phases: the first pass deletes deletable items, and
2834
2901
* the second one rebuilds the rebuildable items. This is essential for
@@ -2846,16 +2913,22 @@ RelationCacheInvalidateEntry(Oid relationId)
2846
2913
* second pass processes nailed-in-cache items before other nondeletable
2847
2914
* items. This should ensure that system catalogs are up to date before
2848
2915
* we attempt to use them to reload information about other open relations.
2916
+ *
2917
+ * After those two phases of work having immediate effects, we normally
2918
+ * signal any RelationBuildDesc() on the stack to start over. However, we
2919
+ * don't do this if called as part of debug_discard_caches. Otherwise,
2920
+ * RelationBuildDesc() would become an infinite loop.
2849
2921
*/
2850
2922
void
2851
- RelationCacheInvalidate (void )
2923
+ RelationCacheInvalidate (bool debug_discard )
2852
2924
{
2853
2925
HASH_SEQ_STATUS status ;
2854
2926
RelIdCacheEnt * idhentry ;
2855
2927
Relation relation ;
2856
2928
List * rebuildFirstList = NIL ;
2857
2929
List * rebuildList = NIL ;
2858
2930
ListCell * l ;
2931
+ int i ;
2859
2932
2860
2933
/*
2861
2934
* Reload relation mapping data before starting to reconstruct cache.
@@ -2942,6 +3015,11 @@ RelationCacheInvalidate(void)
2942
3015
RelationClearRelation (relation , true);
2943
3016
}
2944
3017
list_free (rebuildList );
3018
+
3019
+ if (!debug_discard )
3020
+ /* Any RelationBuildDesc() on the stack must start over. */
3021
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
3022
+ in_progress_list [i ].invalidated = true;
2945
3023
}
2946
3024
2947
3025
/*
@@ -3092,6 +3170,13 @@ AtEOXact_RelationCache(bool isCommit)
3092
3170
RelIdCacheEnt * idhentry ;
3093
3171
int i ;
3094
3172
3173
+ /*
3174
+ * Forget in_progress_list. This is relevant when we're aborting due to
3175
+ * an error during RelationBuildDesc().
3176
+ */
3177
+ Assert (in_progress_list_len == 0 || !isCommit );
3178
+ in_progress_list_len = 0 ;
3179
+
3095
3180
/*
3096
3181
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3097
3182
* listed in it. Otherwise fall back on a hash_seq_search scan.
@@ -3238,6 +3323,14 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
3238
3323
RelIdCacheEnt * idhentry ;
3239
3324
int i ;
3240
3325
3326
+ /*
3327
+ * Forget in_progress_list. This is relevant when we're aborting due to
3328
+ * an error during RelationBuildDesc(). We don't commit subtransactions
3329
+ * during RelationBuildDesc().
3330
+ */
3331
+ Assert (in_progress_list_len == 0 || !isCommit );
3332
+ in_progress_list_len = 0 ;
3333
+
3241
3334
/*
3242
3335
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3243
3336
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
@@ -3786,6 +3879,7 @@ void
3786
3879
RelationCacheInitialize (void )
3787
3880
{
3788
3881
HASHCTL ctl ;
3882
+ int allocsize ;
3789
3883
3790
3884
/*
3791
3885
* make sure cache memory context exists
@@ -3802,6 +3896,15 @@ RelationCacheInitialize(void)
3802
3896
RelationIdCache = hash_create ("Relcache by OID" , INITRELCACHESIZE ,
3803
3897
& ctl , HASH_ELEM | HASH_BLOBS );
3804
3898
3899
+ /*
3900
+ * reserve enough in_progress_list slots for many cases
3901
+ */
3902
+ allocsize = 4 ;
3903
+ in_progress_list =
3904
+ MemoryContextAlloc (CacheMemoryContext ,
3905
+ allocsize * sizeof (* in_progress_list ));
3906
+ in_progress_list_maxlen = allocsize ;
3907
+
3805
3908
/*
3806
3909
* relation mapper needs to be initialized too
3807
3910
*/
0 commit comments