mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
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
This commit is contained in:
54
src/backend/utils/cache/partcache.c
vendored
54
src/backend/utils/cache/partcache.c
vendored
@ -41,11 +41,11 @@ static List *generate_partition_qual(Relation rel);
|
||||
|
||||
/*
|
||||
* RelationBuildPartitionKey
|
||||
* Build and attach to relcache partition key data of relation
|
||||
* Build partition key data of relation, and attach to relcache
|
||||
*
|
||||
* Partitioning key data is a complex structure; to avoid complicated logic to
|
||||
* free individual elements whenever the relcache entry is flushed, we give it
|
||||
* its own memory context, child of CacheMemoryContext, which can easily be
|
||||
* its own memory context, a child of CacheMemoryContext, which can easily be
|
||||
* deleted on its own. To avoid leaking memory in that context in case of an
|
||||
* error partway through this function, the context is initially created as a
|
||||
* child of CurTransactionContext and only re-parented to CacheMemoryContext
|
||||
@ -144,6 +144,7 @@ RelationBuildPartitionKey(Relation relation)
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
/* Allocate assorted arrays in the partkeycxt, which we'll fill below */
|
||||
oldcxt = MemoryContextSwitchTo(partkeycxt);
|
||||
key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
|
||||
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
||||
@ -151,8 +152,6 @@ RelationBuildPartitionKey(Relation relation)
|
||||
key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
|
||||
|
||||
key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
||||
|
||||
/* Gather type and collation info as well */
|
||||
key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
|
||||
key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
|
||||
key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16));
|
||||
@ -235,6 +234,10 @@ RelationBuildPartitionKey(Relation relation)
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
/* Assert that we're not leaking any old data during assignments below */
|
||||
Assert(relation->rd_partkeycxt == NULL);
|
||||
Assert(relation->rd_partkey == NULL);
|
||||
|
||||
/*
|
||||
* Success --- reparent our context and make the relcache point to the
|
||||
* newly constructed key
|
||||
@ -305,11 +308,9 @@ get_partition_qual_relid(Oid relid)
|
||||
* Generate partition predicate from rel's partition bound expression. The
|
||||
* function returns a NIL list if there is no predicate.
|
||||
*
|
||||
* Result expression tree is stored CacheMemoryContext to ensure it survives
|
||||
* as long as the relcache entry. But we should be running in a less long-lived
|
||||
* working context. To avoid leaking cache memory if this routine fails partway
|
||||
* through, we build in working memory and then copy the completed structure
|
||||
* into cache memory.
|
||||
* We cache a copy of the result in the relcache entry, after constructing
|
||||
* it using the caller's context. This approach avoids leaking any data
|
||||
* into long-lived cache contexts, especially if we fail partway through.
|
||||
*/
|
||||
static List *
|
||||
generate_partition_qual(Relation rel)
|
||||
@ -326,8 +327,8 @@ generate_partition_qual(Relation rel)
|
||||
/* Guard against stack overflow due to overly deep partition tree */
|
||||
check_stack_depth();
|
||||
|
||||
/* Quick copy */
|
||||
if (rel->rd_partcheck != NIL)
|
||||
/* If we already cached the result, just return a copy */
|
||||
if (rel->rd_partcheckvalid)
|
||||
return copyObject(rel->rd_partcheck);
|
||||
|
||||
/* Grab at least an AccessShareLock on the parent table */
|
||||
@ -373,13 +374,36 @@ generate_partition_qual(Relation rel)
|
||||
if (found_whole_row)
|
||||
elog(ERROR, "unexpected whole-row reference found in partition key");
|
||||
|
||||
/* Save a copy in the relcache */
|
||||
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
|
||||
rel->rd_partcheck = copyObject(result);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
/* Assert that we're not leaking any old data during assignments below */
|
||||
Assert(rel->rd_partcheckcxt == NULL);
|
||||
Assert(rel->rd_partcheck == NIL);
|
||||
|
||||
/*
|
||||
* Save a copy in the relcache. The order of these operations is fairly
|
||||
* critical to avoid memory leaks and ensure that we don't leave a corrupt
|
||||
* relcache entry if we fail partway through copyObject.
|
||||
*
|
||||
* If, as is definitely possible, the partcheck list is NIL, then we do
|
||||
* not need to make a context to hold it.
|
||||
*/
|
||||
if (result != NIL)
|
||||
{
|
||||
rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
|
||||
"partition constraint",
|
||||
ALLOCSET_SMALL_SIZES);
|
||||
MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
|
||||
RelationGetRelationName(rel));
|
||||
oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt);
|
||||
rel->rd_partcheck = copyObject(result);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
else
|
||||
rel->rd_partcheck = NIL;
|
||||
rel->rd_partcheckvalid = true;
|
||||
|
||||
/* Keep the parent locked until commit */
|
||||
relation_close(parent, NoLock);
|
||||
|
||||
/* Return the working copy to the caller */
|
||||
return result;
|
||||
}
|
||||
|
Reference in New Issue
Block a user