Improve generate_partition_qual()'s cache handling on out-of-memory errors
authorMichael Paquier <michael@paquier.xyz>
Tue, 21 Jul 2026 07:50:18 +0000 (16:50 +0900)
committerMichael Paquier <michael@paquier.xyz>
Tue, 21 Jul 2026 07:50:18 +0000 (16:50 +0900)
An in-flight failure when trying to set rd_partcheckcxt or
rd_partcheck, while for example doing an allocation in copyObject(),
would leave a backend cache in a corrupted state.  The operations are
now ordered so as we avoid a leak in the cache memory context and a
semi-filled cache state when an allocation failure happens.

This is unlikely going to be hit in practice.  Like the other
improvements of this kind, no backpatch is done.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Matthias van de Meent <boekewurm+postgres@gmail.com>
Discussion: https://postgr.es/m/95c64dc2-3abe-4f4e-b285-4c681f565d9f@gmail.com

src/backend/utils/cache/partcache.c

index 3107075c9ad6cb911ee2a605b52d22348618a54f..a0982674884cf0b6f86169759cf0d993f5f7c006 100644 (file)
@@ -411,14 +411,31 @@ generate_partition_qual(Relation rel)
     */
    if (result != NIL)
    {
-       rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
-                                                    "partition constraint",
-                                                    ALLOCSET_SMALL_SIZES);
-       MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
+       /*
+        * Take care to order operations so that allocation errors don't leave
+        * the catcache in an invalid state; first allocate everything into a
+        * transactional context, then associate it with CacheContext and
+        * update the relation data.  This also avoids leaking memory if we
+        * ever hit OOM here.
+        */
+       List       *partcheck;
+       MemoryContext partctx;
+
+       partctx = AllocSetContextCreate(CurrentMemoryContext,
+                                       "partition constraint",
+                                       ALLOCSET_SMALL_SIZES);
+       MemoryContextCopyAndSetIdentifier(partctx,
                                          RelationGetRelationName(rel));
-       oldcxt = MemoryContextSwitchTo(rel->rd_partcheckcxt);
-       rel->rd_partcheck = copyObject(result);
+
+       oldcxt = MemoryContextSwitchTo(partctx);
+       partcheck = copyObject(result);
        MemoryContextSwitchTo(oldcxt);
+
+       /* finally, link the allocations and memctx into the right places */
+       MemoryContextSetParent(partctx, CacheMemoryContext);
+
+       rel->rd_partcheckcxt = partctx;
+       rel->rd_partcheck = partcheck;
    }
    else
        rel->rd_partcheck = NIL;