@@ -144,6 +144,24 @@ bool criticalSharedRelcachesBuilt = false;
144
144
*/
145
145
static long relcacheInvalsReceived = 0L ;
146
146
147
+ /*
148
+ * in_progress_list is a stack of ongoing RelationBuildDesc() calls. CREATE
149
+ * INDEX CONCURRENTLY makes catalog changes under ShareUpdateExclusiveLock.
150
+ * It critically relies on each backend absorbing those changes no later than
151
+ * next transaction start. Hence, RelationBuildDesc() loops until it finishes
152
+ * without accepting a relevant invalidation. (Most invalidation consumers
153
+ * don't do this.)
154
+ */
155
+ typedef struct inprogressent
156
+ {
157
+ Oid reloid ; /* OID of relation being built */
158
+ bool invalidated ; /* whether an invalidation arrived for it */
159
+ } InProgressEnt ;
160
+
161
+ static InProgressEnt * in_progress_list ;
162
+ static int in_progress_list_len ;
163
+ static int in_progress_list_maxlen ;
164
+
147
165
/*
148
166
* eoxact_list[] stores the OIDs of relations that (might) need AtEOXact
149
167
* cleanup work. This list intentionally has limited size; if it overflows,
@@ -1088,11 +1106,27 @@ equalPartitionDescs(PartitionKey key, PartitionDesc partdesc1,
1088
1106
static Relation
1089
1107
RelationBuildDesc (Oid targetRelId , bool insertIt )
1090
1108
{
1109
+ int in_progress_offset ;
1091
1110
Relation relation ;
1092
1111
Oid relid ;
1093
1112
HeapTuple pg_class_tuple ;
1094
1113
Form_pg_class relp ;
1095
1114
1115
+ /* Register to catch invalidation messages */
1116
+ if (in_progress_list_len >= in_progress_list_maxlen )
1117
+ {
1118
+ int allocsize ;
1119
+
1120
+ allocsize = in_progress_list_maxlen * 2 ;
1121
+ in_progress_list = repalloc (in_progress_list ,
1122
+ allocsize * sizeof (* in_progress_list ));
1123
+ in_progress_list_maxlen = allocsize ;
1124
+ }
1125
+ in_progress_offset = in_progress_list_len ++ ;
1126
+ in_progress_list [in_progress_offset ].reloid = targetRelId ;
1127
+ retry :
1128
+ in_progress_list [in_progress_offset ].invalidated = false;
1129
+
1096
1130
/*
1097
1131
* find the tuple in pg_class corresponding to the given relation id
1098
1132
*/
@@ -1102,7 +1136,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1102
1136
* if no such tuple exists, return NULL
1103
1137
*/
1104
1138
if (!HeapTupleIsValid (pg_class_tuple ))
1139
+ {
1140
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1141
+ in_progress_list_len -- ;
1105
1142
return NULL ;
1143
+ }
1106
1144
1107
1145
/*
1108
1146
* get information from the pg_class_tuple
@@ -1246,6 +1284,21 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
1246
1284
*/
1247
1285
heap_freetuple (pg_class_tuple );
1248
1286
1287
+ /*
1288
+ * If an invalidation arrived mid-build, start over. Between here and the
1289
+ * end of this function, don't add code that does or reasonably could read
1290
+ * system catalogs. That range must be free from invalidation processing
1291
+ * for the !insertIt case. For the insertIt case, RelationCacheInsert()
1292
+ * will enroll this relation in ordinary relcache invalidation processing,
1293
+ */
1294
+ if (in_progress_list [in_progress_offset ].invalidated )
1295
+ {
1296
+ RelationDestroyRelation (relation , false);
1297
+ goto retry ;
1298
+ }
1299
+ Assert (in_progress_offset + 1 == in_progress_list_len );
1300
+ in_progress_list_len -- ;
1301
+
1249
1302
/*
1250
1303
* Insert newly created relation into relcache hash table, if requested.
1251
1304
*
@@ -2469,6 +2522,14 @@ RelationClearRelation(Relation relation, bool rebuild)
2469
2522
2470
2523
/* Build temporary entry, but don't link it into hashtable */
2471
2524
newrel = RelationBuildDesc (save_relid , false);
2525
+
2526
+ /*
2527
+ * Between here and the end of the swap, don't add code that does or
2528
+ * reasonably could read system catalogs. That range must be free
2529
+ * from invalidation processing. See RelationBuildDesc() manipulation
2530
+ * of in_progress_list.
2531
+ */
2532
+
2472
2533
if (newrel == NULL )
2473
2534
{
2474
2535
/*
@@ -2660,6 +2721,14 @@ RelationCacheInvalidateEntry(Oid relationId)
2660
2721
relcacheInvalsReceived ++ ;
2661
2722
RelationFlushRelation (relation );
2662
2723
}
2724
+ else
2725
+ {
2726
+ int i ;
2727
+
2728
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2729
+ if (in_progress_list [i ].reloid == relationId )
2730
+ in_progress_list [i ].invalidated = true;
2731
+ }
2663
2732
}
2664
2733
2665
2734
/*
@@ -2691,16 +2760,22 @@ RelationCacheInvalidateEntry(Oid relationId)
2691
2760
* second pass processes nailed-in-cache items before other nondeletable
2692
2761
* items. This should ensure that system catalogs are up to date before
2693
2762
* we attempt to use them to reload information about other open relations.
2763
+ *
2764
+ * After those two phases of work having immediate effects, we normally
2765
+ * signal any RelationBuildDesc() on the stack to start over. However, we
2766
+ * don't do this if called as part of debug_discard_caches. Otherwise,
2767
+ * RelationBuildDesc() would become an infinite loop.
2694
2768
*/
2695
2769
void
2696
- RelationCacheInvalidate (void )
2770
+ RelationCacheInvalidate (bool debug_discard )
2697
2771
{
2698
2772
HASH_SEQ_STATUS status ;
2699
2773
RelIdCacheEnt * idhentry ;
2700
2774
Relation relation ;
2701
2775
List * rebuildFirstList = NIL ;
2702
2776
List * rebuildList = NIL ;
2703
2777
ListCell * l ;
2778
+ int i ;
2704
2779
2705
2780
/*
2706
2781
* Reload relation mapping data before starting to reconstruct cache.
@@ -2787,6 +2862,11 @@ RelationCacheInvalidate(void)
2787
2862
RelationClearRelation (relation , true);
2788
2863
}
2789
2864
list_free (rebuildList );
2865
+
2866
+ if (!debug_discard )
2867
+ /* Any RelationBuildDesc() on the stack must start over. */
2868
+ for (i = 0 ; i < in_progress_list_len ; i ++ )
2869
+ in_progress_list [i ].invalidated = true;
2790
2870
}
2791
2871
2792
2872
/*
@@ -2859,6 +2939,13 @@ AtEOXact_RelationCache(bool isCommit)
2859
2939
RelIdCacheEnt * idhentry ;
2860
2940
int i ;
2861
2941
2942
+ /*
2943
+ * Forget in_progress_list. This is relevant when we're aborting due to
2944
+ * an error during RelationBuildDesc().
2945
+ */
2946
+ Assert (in_progress_list_len == 0 || !isCommit );
2947
+ in_progress_list_len = 0 ;
2948
+
2862
2949
/*
2863
2950
* Unless the eoxact_list[] overflowed, we only need to examine the rels
2864
2951
* listed in it. Otherwise fall back on a hash_seq_search scan.
@@ -3008,6 +3095,14 @@ AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
3008
3095
RelIdCacheEnt * idhentry ;
3009
3096
int i ;
3010
3097
3098
+ /*
3099
+ * Forget in_progress_list. This is relevant when we're aborting due to
3100
+ * an error during RelationBuildDesc(). We don't commit subtransactions
3101
+ * during RelationBuildDesc().
3102
+ */
3103
+ Assert (in_progress_list_len == 0 || !isCommit );
3104
+ in_progress_list_len = 0 ;
3105
+
3011
3106
/*
3012
3107
* Unless the eoxact_list[] overflowed, we only need to examine the rels
3013
3108
* listed in it. Otherwise fall back on a hash_seq_search scan. Same
@@ -3497,6 +3592,7 @@ void
3497
3592
RelationCacheInitialize (void )
3498
3593
{
3499
3594
HASHCTL ctl ;
3595
+ int allocsize ;
3500
3596
3501
3597
/*
3502
3598
* make sure cache memory context exists
@@ -3513,6 +3609,15 @@ RelationCacheInitialize(void)
3513
3609
RelationIdCache = hash_create ("Relcache by OID" , INITRELCACHESIZE ,
3514
3610
& ctl , HASH_ELEM | HASH_BLOBS );
3515
3611
3612
+ /*
3613
+ * reserve enough in_progress_list slots for many cases
3614
+ */
3615
+ allocsize = 4 ;
3616
+ in_progress_list =
3617
+ MemoryContextAlloc (CacheMemoryContext ,
3618
+ allocsize * sizeof (* in_progress_list ));
3619
+ in_progress_list_maxlen = allocsize ;
3620
+
3516
3621
/*
3517
3622
* relation mapper needs to be initialized too
3518
3623
*/
0 commit comments