@@ -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,
@@ -1000,6 +1018,7 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
1000
1018
static Relation
1001
1019
RelationBuildDesc (Oid targetRelId , bool insertIt )
1002
1020
{
1021
+ int in_progress_offset ;
1003
1022
Relation relation ;
1004
1023
Oid relid ;
1005
1024
HeapTuple pg_class_tuple ;
@@ -1033,6 +1052,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1033
1052
}
1034
1053
#endif
1035
1054
1055
+ /* Register to catch invalidation messages */
1056
+ if (in_progress_list_len >= in_progress_list_maxlen )
1057
+ {
1058
+ int allocsize ;
1059
+
1060
+ allocsize = in_progress_list_maxlen * 2 ;
1061
+ in_progress_list = repalloc (in_progress_list ,
1062
+ allocsize * sizeof (* in_progress_list ));
1063
+ in_progress_list_maxlen = allocsize ;
1064
+ }
1065
+ in_progress_offset = in_progress_list_len ++ ;
1066
+ in_progress_list [in_progress_offset ].reloid = targetRelId ;
1067
+ retry :
1068
+ in_progress_list [in_progress_offset ].invalidated = false;
1069
+
1036
1070
/*
1037
1071
* find the tuple in pg_class corresponding to the given relation id
1038
1072
*/
@@ -1051,6 +1085,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1051
1085
MemoryContextDelete (tmpcxt );
1052
1086
}
1053
1087
#endif
1088
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1089
+ in_progress_list_len -- ;
1054
1090
return NULL ;
1055
1091
}
1056
1092
@@ -1213,6 +1249,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1213
1249
*/
1214
1250
heap_freetuple (pg_class_tuple );
1215
1251
1252
+ /*
1253
+ * If an invalidation arrived mid-build, start over. Between here and the
1254
+ * end of this function, don't add code that does or reasonably could read
1255
+ * system catalogs. That range must be free from invalidation processing
1256
+ * for the !insertIt case. For the insertIt case, RelationCacheInsert()
1257
+ * will enroll this relation in ordinary relcache invalidation processing,
1258
+ */
1259
+ if (in_progress_list [in_progress_offset ].invalidated )
1260
+ {
1261
+ RelationDestroyRelation (relation , false);
1262
+ goto retry ;
1263
+ }
1264
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1265
+ in_progress_list_len -- ;
1266
+
1216
1267
/*
1217
1268
* Insert newly created relation into relcache hash table, if requested.
1218
1269
*
@@ -2566,6 +2617,14 @@ RelationClearRelation(Relation relation, bool rebuild)
2566
2617
2567
2618
/* Build temporary entry, but don't link it into hashtable */
2568
2619
newrel = RelationBuildDesc (save_relid , false);
2620
+
2621
+ /*
2622
+ * Between here and the end of the swap, don't add code that does or
2623
+ * reasonably could read system catalogs. That range must be free
2624
+ * from invalidation processing. See RelationBuildDesc() manipulation
2625
+ * of in_progress_list.
2626
+ */
2627
+
2569
2628
if (newrel == NULL )
2570
2629
{
2571
2630
/*
@@ -2805,6 +2864,14 @@ RelationCacheInvalidateEntry(Oid relationId)
2805
2864
relcacheInvalsReceived ++ ;
2806
2865
RelationFlushRelation (relation );
2807
2866
}
2867
+ else
2868
+ {
2869
+ int i ;
2870
+
2871
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2872
+ if (in_progress_list [i ].reloid == relationId )
2873
+ in_progress_list [i ].invalidated = true;
2874
+ }
2808
2875
}
2809
2876
2810
2877
/*
@@ -2813,11 +2880,11 @@ RelationCacheInvalidateEntry(Oid relationId)
2813
2880
* and rebuild those with positive reference counts. Also reset the smgr
2814
2881
* relation cache and re-read relation mapping data.
2815
2882
*
2816
- * This is currently used only to recover from SI message buffer overflow,
2817
- * so we do not touch relations having new-in-transaction relfilenodes; they
2818
- * cannot be targets of cross-backend SI updates (and our own updates now go
2819
- * through a separate linked list that isn't limited by the SI message
2820
- * buffer size).
2883
+ * Apart from debug_discard_caches, this is currently used only to recover
2884
+ * from SI message buffer overflow, so we do not touch relations having
2885
+ * new-in-transaction relfilenodes; they cannot be targets of cross-backend
2886
+ * SI updates (and our own updates now go through a separate linked list
2887
+ * that isn't limited by the SI message buffer size).
2821
2888
*
2822
2889
* We do this in two phases: the first pass deletes deletable items, and
2823
2890
* the second one rebuilds the rebuildable items. This is essential for
@@ -2835,16 +2902,22 @@ RelationCacheInvalidateEntry(Oid relationId)
2835
2902
* second pass processes nailed-in-cache items before other nondeletable
2836
2903
* items. This should ensure that system catalogs are up to date before
2837
2904
* we attempt to use them to reload information about other open relations.
2905
+ *
2906
+ * After those two phases of work having immediate effects, we normally
2907
+ * signal any RelationBuildDesc() on the stack to start over. However, we
2908
+ * don't do this if called as part of debug_discard_caches. Otherwise,
2909
+ * RelationBuildDesc() would become an infinite loop.
2838
2910
*/
2839
2911
void
2840
- RelationCacheInvalidate (void )
2912
+ RelationCacheInvalidate (bool debug_discard )
2841
2913
{
2842
2914
HASH_SEQ_STATUS status ;
2843
2915
RelIdCacheEnt * idhentry ;
2844
2916
Relation relation ;
2845
2917
List * rebuildFirstList = NIL ;
2846
2918
List * rebuildList = NIL ;
2847
2919
ListCell * l ;
2920
+ int i ;
2848
2921
2849
2922
/*
2850
2923
* Reload relation mapping data before starting to reconstruct cache.
@@ -2931,6 +3004,11 @@ RelationCacheInvalidate(void)
2931
3004
RelationClearRelation (relation , true);
2932
3005
}
2933
3006
list_free (rebuildList );
3007
+
3008
+ if (!debug_discard )
3009
+ /* Any RelationBuildDesc() on the stack must start over. */
3010
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
3011
+ in_progress_list [i ].invalidated = true;
2934
3012
}
2935
3013
2936
3014
/*
@@ -3081,6 +3159,13 @@ AtEOXact_RelationCache(bool isCommit)
3081
3159
RelIdCacheEnt * idhentry ;
3082
3160
int i ;
3083
3161
3162
+ /*
3163
+ * Forget in_progress_list. This is relevant when we're aborting due to
3164
+ * an error during RelationBuildDesc().
3165
+ */
3166
+ Assert (in_progress_list_len == 0 || !isCommit );
3167
+ in_progress_list_len = 0 ;
3168
+
3084
3169
/*
3085
3170
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3086
3171
* listed in it. Otherwise fall back on a hash_seq_search scan.
@@ -3227,6 +3312,14 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
3227
3312
RelIdCacheEnt * idhentry ;
3228
3313
int i ;
3229
3314
3315
+ /*
3316
+ * Forget in_progress_list. This is relevant when we're aborting due to
3317
+ * an error during RelationBuildDesc(). We don't commit subtransactions
3318
+ * during RelationBuildDesc().
3319
+ */
3320
+ Assert (in_progress_list_len == 0 || !isCommit );
3321
+ in_progress_list_len = 0 ;
3322
+
3230
3323
/*
3231
3324
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3232
3325
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
@@ -3775,6 +3868,7 @@ void
3775
3868
RelationCacheInitialize (void )
3776
3869
{
3777
3870
HASHCTL ctl ;
3871
+ int allocsize ;
3778
3872
3779
3873
/*
3780
3874
* make sure cache memory context exists
@@ -3790,6 +3884,15 @@ RelationCacheInitialize(void)
3790
3884
RelationIdCache = hash_create ("Relcache by OID" , INITRELCACHESIZE ,
3791
3885
& ctl , HASH_ELEM | HASH_BLOBS );
3792
3886
3887
+ /*
3888
+ * reserve enough in_progress_list slots for many cases
3889
+ */
3890
+ allocsize = 4 ;
3891
+ in_progress_list =
3892
+ MemoryContextAlloc (CacheMemoryContext ,
3893
+ allocsize * sizeof (* in_progress_list ));
3894
+ in_progress_list_maxlen = allocsize ;
3895
+
3793
3896
/*
3794
3897
* relation mapper needs to be initialized too
3795
3898
*/
0 commit comments