@@ -47,11 +47,11 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b,
4747
4848/*
4949 * RelationBuildPartitionKey
50- * Build and attach to relcache partition key data of relation
50+ * Build partition key data of relation, and attach to relcache
5151 *
5252 * Partitioning key data is a complex structure; to avoid complicated logic to
5353 * free individual elements whenever the relcache entry is flushed, we give it
54- * its own memory context, child of CacheMemoryContext, which can easily be
54+ * its own memory context, a child of CacheMemoryContext, which can easily be
5555 * deleted on its own. To avoid leaking memory in that context in case of an
5656 * error partway through this function, the context is initially created as a
5757 * child of CurTransactionContext and only re-parented to CacheMemoryContext
@@ -150,15 +150,14 @@ RelationBuildPartitionKey(Relation relation)
150150 MemoryContextSwitchTo (oldcxt );
151151 }
152152
153+ /* Allocate assorted arrays in the partkeycxt, which we'll fill below */
153154 oldcxt = MemoryContextSwitchTo (partkeycxt );
154155 key -> partattrs = (AttrNumber * ) palloc0 (key -> partnatts * sizeof (AttrNumber ));
155156 key -> partopfamily = (Oid * ) palloc0 (key -> partnatts * sizeof (Oid ));
156157 key -> partopcintype = (Oid * ) palloc0 (key -> partnatts * sizeof (Oid ));
157158 key -> partsupfunc = (FmgrInfo * ) palloc0 (key -> partnatts * sizeof (FmgrInfo ));
158159
159160 key -> partcollation = (Oid * ) palloc0 (key -> partnatts * sizeof (Oid ));
160-
161- /* Gather type and collation info as well */
162161 key -> parttypid = (Oid * ) palloc0 (key -> partnatts * sizeof (Oid ));
163162 key -> parttypmod = (int32 * ) palloc0 (key -> partnatts * sizeof (int32 ));
164163 key -> parttyplen = (int16 * ) palloc0 (key -> partnatts * sizeof (int16 ));
@@ -241,6 +240,10 @@ RelationBuildPartitionKey(Relation relation)
241240
242241 ReleaseSysCache (tuple );
243242
243+ /* Assert that we're not leaking any old data during assignments below */
244+ Assert (relation -> rd_partkeycxt == NULL );
245+ Assert (relation -> rd_partkey == NULL );
246+
244247 /*
245248 * Success --- reparent our context and make the relcache point to the
246249 * newly constructed key
@@ -252,10 +255,13 @@ RelationBuildPartitionKey(Relation relation)
252255
253256/*
254257 * RelationBuildPartitionDesc
255- * Form rel's partition descriptor
258+ * Form rel's partition descriptor, and store in relcache entry
256259 *
257- * Not flushed from the cache by RelationClearRelation() unless changed because
258- * of addition or removal of partition.
260+ * Note: the descriptor won't be flushed from the cache by
261+ * RelationClearRelation() unless it's changed because of
262+ * addition or removal of a partition. Hence, code holding a lock
263+ * that's sufficient to prevent that can assume that rd_partdesc
264+ * won't change underneath it.
259265 */
260266void
261267RelationBuildPartitionDesc (Relation rel )
@@ -565,11 +571,24 @@ RelationBuildPartitionDesc(Relation rel)
565571 (int ) key -> strategy );
566572 }
567573
568- /* Now build the actual relcache partition descriptor */
574+ /* Assert we aren't about to leak any old data structure */
575+ Assert (rel -> rd_pdcxt == NULL );
576+ Assert (rel -> rd_partdesc == NULL );
577+
578+ /*
579+ * Now build the actual relcache partition descriptor. Note that the
580+ * order of operations here is fairly critical. If we fail partway
581+ * through this code, we won't have leaked memory because the rd_pdcxt is
582+ * attached to the relcache entry immediately, so it'll be freed whenever
583+ * the entry is rebuilt or destroyed. However, we don't assign to
584+ * rd_partdesc until the cached data structure is fully complete and
585+ * valid, so that no other code might try to use it.
586+ */
569587 rel -> rd_pdcxt = AllocSetContextCreate (CacheMemoryContext ,
570588 "partition descriptor" ,
571- ALLOCSET_DEFAULT_SIZES );
572- MemoryContextCopyAndSetIdentifier (rel -> rd_pdcxt , RelationGetRelationName (rel ));
589+ ALLOCSET_SMALL_SIZES );
590+ MemoryContextCopyAndSetIdentifier (rel -> rd_pdcxt ,
591+ RelationGetRelationName (rel ));
573592
574593 oldcxt = MemoryContextSwitchTo (rel -> rd_pdcxt );
575594
@@ -839,11 +858,9 @@ get_partition_qual_relid(Oid relid)
839858 * Generate partition predicate from rel's partition bound expression. The
840859 * function returns a NIL list if there is no predicate.
841860 *
842- * Result expression tree is stored CacheMemoryContext to ensure it survives
843- * as long as the relcache entry. But we should be running in a less long-lived
844- * working context. To avoid leaking cache memory if this routine fails partway
845- * through, we build in working memory and then copy the completed structure
846- * into cache memory.
861+ * We cache a copy of the result in the relcache entry, after constructing
862+ * it using the caller's context. This approach avoids leaking any data
863+ * into long-lived cache contexts, especially if we fail partway through.
847864 */
848865static List *
849866generate_partition_qual (Relation rel )
@@ -860,8 +877,8 @@ generate_partition_qual(Relation rel)
860877 /* Guard against stack overflow due to overly deep partition tree */
861878 check_stack_depth ();
862879
863- /* Quick copy */
864- if (rel -> rd_partcheck != NIL )
880+ /* If we already cached the result, just return a copy */
881+ if (rel -> rd_partcheckvalid )
865882 return copyObject (rel -> rd_partcheck );
866883
867884 /* Grab at least an AccessShareLock on the parent table */
@@ -907,14 +924,37 @@ generate_partition_qual(Relation rel)
907924 if (found_whole_row )
908925 elog (ERROR , "unexpected whole-row reference found in partition key" );
909926
910- /* Save a copy in the relcache */
911- oldcxt = MemoryContextSwitchTo (CacheMemoryContext );
912- rel -> rd_partcheck = copyObject (result );
913- MemoryContextSwitchTo (oldcxt );
927+ /* Assert that we're not leaking any old data during assignments below */
928+ Assert (rel -> rd_partcheckcxt == NULL );
929+ Assert (rel -> rd_partcheck == NIL );
930+
931+ /*
932+ * Save a copy in the relcache. The order of these operations is fairly
933+ * critical to avoid memory leaks and ensure that we don't leave a corrupt
934+ * relcache entry if we fail partway through copyObject.
935+ *
936+ * If, as is definitely possible, the partcheck list is NIL, then we do
937+ * not need to make a context to hold it.
938+ */
939+ if (result != NIL )
940+ {
941+ rel -> rd_partcheckcxt = AllocSetContextCreate (CacheMemoryContext ,
942+ "partition constraint" ,
943+ ALLOCSET_SMALL_SIZES );
944+ MemoryContextCopyAndSetIdentifier (rel -> rd_partcheckcxt ,
945+ RelationGetRelationName (rel ));
946+ oldcxt = MemoryContextSwitchTo (rel -> rd_partcheckcxt );
947+ rel -> rd_partcheck = copyObject (result );
948+ MemoryContextSwitchTo (oldcxt );
949+ }
950+ else
951+ rel -> rd_partcheck = NIL ;
952+ rel -> rd_partcheckvalid = true;
914953
915954 /* Keep the parent locked until commit */
916955 relation_close (parent , NoLock );
917956
957+ /* Return the working copy to the caller */
918958 return result ;
919959}
920960
0 commit comments