Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5f1433a

Browse files
committed
Prevent memory leaks associated with relcache rd_partcheck structures.
The original coding of generate_partition_qual() just copied the list of predicate expressions into the global CacheMemoryContext, making it effectively impossible to clean up when the owning relcache entry is destroyed --- the relevant code in RelationDestroyRelation() only managed to free the topmost List header :-(. This resulted in a session-lifespan memory leak whenever a table partition's relcache entry is rebuilt. Fortunately, that's not normally a large data structure, and rebuilds shouldn't occur all that often in production situations; but this is still a bug worth fixing back to v10 where the code was introduced. To fix, put the cached expression tree into its own small memory context, as we do with other complicated substructures of relcache entries. Also, deal more honestly with the case that a partition has an empty partcheck list; while that probably isn't a case that's very interesting for production use, it's legal. In passing, clarify comments about how partitioning-related relcache data structures are managed, and add some Asserts that we're not leaking old copies when we overwrite these data fields. Amit Langote and Tom Lane Discussion: https://postgr.es/m/7961.1552498252@sss.pgh.pa.us
1 parent c098509 commit 5f1433a

File tree

4 files changed

+75
-28
lines changed

4 files changed

+75
-28
lines changed

src/backend/partitioning/partdesc.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@ typedef struct PartitionDirectoryEntry
4949

5050
/*
5151
* RelationBuildPartitionDesc
52-
* Form rel's partition descriptor
52+
* Form rel's partition descriptor, and store in relcache entry
5353
*
54-
* Not flushed from the cache by RelationClearRelation() unless changed because
55-
* of addition or removal of partition.
54+
* Note: the descriptor won't be flushed from the cache by
55+
* RelationClearRelation() unless it's changed because of
56+
* addition or removal of a partition. Hence, code holding a lock
57+
* that's sufficient to prevent that can assume that rd_partdesc
58+
* won't change underneath it.
5659
*/
5760
void
5861
RelationBuildPartitionDesc(Relation rel)
@@ -173,7 +176,19 @@ RelationBuildPartitionDesc(Relation rel)
173176
++i;
174177
}
175178

176-
/* Now build the actual relcache partition descriptor */
179+
/* Assert we aren't about to leak any old data structure */
180+
Assert(rel->rd_pdcxt == NULL);
181+
Assert(rel->rd_partdesc == NULL);
182+
183+
/*
184+
* Now build the actual relcache partition descriptor. Note that the
185+
* order of operations here is fairly critical. If we fail partway
186+
* through this code, we won't have leaked memory because the rd_pdcxt is
187+
* attached to the relcache entry immediately, so it'll be freed whenever
188+
* the entry is rebuilt or destroyed. However, we don't assign to
189+
* rd_partdesc until the cached data structure is fully complete and
190+
* valid, so that no other code might try to use it.
191+
*/
177192
rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
178193
"partition descriptor",
179194
ALLOCSET_SMALL_SIZES);

src/backend/utils/cache/partcache.c

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ static List *generate_partition_qual(Relation rel);
4141

4242
/*
4343
* RelationBuildPartitionKey
44-
* Build and attach to relcache partition key data of relation
44+
* Build partition key data of relation, and attach to relcache
4545
*
4646
* Partitioning key data is a complex structure; to avoid complicated logic to
4747
* free individual elements whenever the relcache entry is flushed, we give it
48-
* its own memory context, child of CacheMemoryContext, which can easily be
48+
* its own memory context, a child of CacheMemoryContext, which can easily be
4949
* deleted on its own. To avoid leaking memory in that context in case of an
5050
* error partway through this function, the context is initially created as a
5151
* child of CurTransactionContext and only re-parented to CacheMemoryContext
@@ -144,15 +144,14 @@ RelationBuildPartitionKey(Relation relation)
144144
MemoryContextSwitchTo(oldcxt);
145145
}
146146

147+
/* Allocate assorted arrays in the partkeycxt, which we'll fill below */
147148
oldcxt = MemoryContextSwitchTo(partkeycxt);
148149
key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
149150
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
150151
key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
151152
key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
152153

153154
key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
154-
155-
/* Gather type and collation info as well */
156155
key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
157156
key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
158157
key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
@@ -235,6 +234,10 @@ RelationBuildPartitionKey(Relation relation)
235234

236235
ReleaseSysCache(tuple);
237236

237+
/* Assert that we're not leaking any old data during assignments below */
238+
Assert(relation->rd_partkeycxt == NULL);
239+
Assert(relation->rd_partkey == NULL);
240+
238241
/*
239242
* Success --- reparent our context and make the relcache point to the
240243
* newly constructed key
@@ -305,11 +308,9 @@ get_partition_qual_relid(Oid relid)
305308
* Generate partition predicate from rel's partition bound expression. The
306309
* function returns a NIL list if there is no predicate.
307310
*
308-
* Result expression tree is stored CacheMemoryContext to ensure it survives
309-
* as long as the relcache entry. But we should be running in a less long-lived
310-
* working context. To avoid leaking cache memory if this routine fails partway
311-
* through, we build in working memory and then copy the completed structure
312-
* into cache memory.
311+
* We cache a copy of the result in the relcache entry, after constructing
312+
* it using the caller's context. This approach avoids leaking any data
313+
* into long-lived cache contexts, especially if we fail partway through.
313314
*/
314315
static List *
315316
generate_partition_qual(Relation rel)
@@ -326,8 +327,8 @@ generate_partition_qual(Relation rel)
326327
/* Guard against stack overflow due to overly deep partition tree */
327328
check_stack_depth();
328329

329-
/* Quick copy */
330-
if (rel->rd_partcheck != NIL)
330+
/* If we already cached the result, just return a copy */
331+
if (rel->rd_partcheckvalid)
331332
return copyObject(rel->rd_partcheck);
332333

333334
/* Grab at least an AccessShareLock on the parent table */
@@ -373,13 +374,36 @@ generate_partition_qual(Relation rel)
373374
if (found_whole_row)
374375
elog(ERROR, "unexpected whole-row reference found in partition key");
375376

376-
/* Save a copy in the relcache */
377-
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
378-
rel->rd_partcheck = copyObject(result);
379-
MemoryContextSwitchTo(oldcxt);
377+
/* Assert that we're not leaking any old data during assignments below */
378+
Assert(rel->rd_partcheckcxt == NULL);
379+
Assert(rel->rd_partcheck == NIL);
380+
381+
/*
382+
* Save a copy in the relcache. The order of these operations is fairly
383+
* critical to avoid memory leaks and ensure that we don't leave a corrupt
384+
* relcache entry if we fail partway through copyObject.
385+
*
386+
* If, as is definitely possible, the partcheck list is NIL, then we do
387+
* not need to make a context to hold it.
388+
*/
389+
if (result != NIL)
390+
{
391+
rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
392+
"partition constraint",
393+
ALLOCSET_SMALL_SIZES);
394+
MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
395+
RelationGetRelationName(rel));
396+
oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt);
397+
rel->rd_partcheck = copyObject(result);
398+
MemoryContextSwitchTo(oldcxt);
399+
}
400+
else
401+
rel->rd_partcheck = NIL;
402+
rel->rd_partcheckvalid = true;
380403

381404
/* Keep the parent locked until commit */
382405
relation_close(parent, NoLock);
383406

407+
/* Return the working copy to the caller */
384408
return result;
385409
}

src/backend/utils/cache/relcache.c

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,11 +1175,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
11751175
}
11761176
else
11771177
{
1178-
relation->rd_partkeycxt = NULL;
11791178
relation->rd_partkey = NULL;
1179+
relation->rd_partkeycxt = NULL;
11801180
relation->rd_partdesc = NULL;
11811181
relation->rd_pdcxt = NULL;
11821182
}
1183+
/* ... but partcheck is not loaded till asked for */
1184+
relation->rd_partcheck = NIL;
1185+
relation->rd_partcheckvalid = false;
1186+
relation->rd_partcheckcxt = NULL;
11831187

11841188
/*
11851189
* initialize access method information
@@ -2364,8 +2368,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
23642368
MemoryContextDelete(relation->rd_partkeycxt);
23652369
if (relation->rd_pdcxt)
23662370
MemoryContextDelete(relation->rd_pdcxt);
2367-
if (relation->rd_partcheck)
2368-
pfree(relation->rd_partcheck);
2371+
if (relation->rd_partcheckcxt)
2372+
MemoryContextDelete(relation->rd_partcheckcxt);
23692373
if (relation->rd_fdwroutine)
23702374
pfree(relation->rd_fdwroutine);
23712375
pfree(relation);
@@ -5600,18 +5604,20 @@ load_relcache_init_file(bool shared)
56005604
* format is complex and subject to change). They must be rebuilt if
56015605
* needed by RelationCacheInitializePhase3. This is not expected to
56025606
* be a big performance hit since few system catalogs have such. Ditto
5603-
* for RLS policy data, index expressions, predicates, exclusion info,
5604-
* and FDW info.
5607+
* for RLS policy data, partition info, index expressions, predicates,
5608+
* exclusion info, and FDW info.
56055609
*/
56065610
rel->rd_rules = NULL;
56075611
rel->rd_rulescxt = NULL;
56085612
rel->trigdesc = NULL;
56095613
rel->rd_rsdesc = NULL;
5610-
rel->rd_partkeycxt = NULL;
56115614
rel->rd_partkey = NULL;
5612-
rel->rd_pdcxt = NULL;
5615+
rel->rd_partkeycxt = NULL;
56135616
rel->rd_partdesc = NULL;
5617+
rel->rd_pdcxt = NULL;
56145618
rel->rd_partcheck = NIL;
5619+
rel->rd_partcheckvalid = false;
5620+
rel->rd_partcheckcxt = NULL;
56155621
rel->rd_indexprs = NIL;
56165622
rel->rd_indpred = NIL;
56175623
rel->rd_exclops = NULL;

src/include/utils/rel.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,13 @@ typedef struct RelationData
9595
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
9696
bool rd_fkeyvalid; /* true if list has been computed */
9797

98-
MemoryContext rd_partkeycxt; /* private memory cxt for the below */
9998
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
100-
MemoryContext rd_pdcxt; /* private context for partdesc */
99+
MemoryContext rd_partkeycxt; /* private context for rd_partkey, if any */
101100
struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
101+
MemoryContext rd_pdcxt; /* private context for rd_partdesc, if any */
102102
List *rd_partcheck; /* partition CHECK quals */
103+
bool rd_partcheckvalid; /* true if list has been computed */
104+
MemoryContext rd_partcheckcxt; /* private cxt for rd_partcheck, if any */
103105

104106
/* data managed by RelationGetIndexList: */
105107
List *rd_indexlist; /* list of OIDs of indexes on relation */

0 commit comments

Comments
 (0)