From fb1042d99a26919376a1ed866e5676ecfcdd74a7 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Fri, 30 Jan 2026 11:10:32 +0800 Subject: [PATCH 1/2] Prevent deadlock between ALTER TABLE recursion and ATTACH PARTITION ALTER TABLE commands that recurse to partitions (e.g. ALTER COLUMN SET DEFAULT) lock partitions in ascending OID order using find_all_inheritors(). In contrast, ALTER TABLE ATTACH PARTITION may lock ancestor tables in reverse OID order while recursively collecting partition constraints. When these two operations run concurrently, this lock order inversion can lead to a deadlock. To avoid this, pre-lock all ancestor partitioned tables in OID order when processing ALTER TABLE ATTACH PARTITION. This ensures a consistent lock ordering before any recursive traversal of the partition hierarchy occurs. The ancestor locks are taken early, via RangeVarCallbackForAlterRelation(), so that they precede the standard ALTER TABLE locking of the target relation. This fixes a reproducible deadlock observed on the current master branch with deep partition hierarchies. Author: Chao Li Reviewed-by: Discussion: https://postgr.es/m/ --- src/backend/catalog/partition.c | 19 ++++++++++++++++++ src/backend/commands/tablecmds.c | 33 ++++++++++++++++++++++++++++++++ src/include/catalog/partition.h | 2 ++ 3 files changed, 54 insertions(+) diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 28f3cade6ff..d9449bca92c 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -26,6 +26,7 @@ #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" #include "rewrite/rewriteManip.h" +#include "storage/lmgr.h" #include "utils/fmgroids.h" #include "utils/partcache.h" #include "utils/rel.h" @@ -145,6 +146,24 @@ get_partition_ancestors(Oid relid) return result; } +/* + * lock_partition_ancestors + * Lock all ancestors of given relation from root to parent. + */ +List * +lock_partition_ancestors(Oid relid, LOCKMODE lockmode) +{ + List *ancestors = get_partition_ancestors(relid); + + for (int i = list_length(ancestors) - 1; i >= 0; i--) + { + Oid parentoid = list_nth_oid(ancestors, i); + + LockRelationOid(parentoid, lockmode); + } + return ancestors; +} + /* * get_partition_ancestors_worker * recursive worker for get_partition_ancestors diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 93ad49e59fd..819babae5c9 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -20443,6 +20443,39 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, errhint("Change the schema of the table instead."))); } + /* + * For ALTER TABLE ATTACH PARTITION, pre-lock all ancestors. That's because + * ATTACH PARTITION needs to find all ancestors' partition bounds, thus it + * will recurse up the partition tree, and lock each ancestor it meets, + * which may lead to deadlock. So we pre-lock all ancestors here to avoid + * that. + */ + if (IsA(stmt, AlterTableStmt)) + { + AlterTableStmt *ats = (AlterTableStmt *) stmt; + ListCell *l; + bool found = false; + + foreach (l, ats->cmds) + { + AlterTableCmd *atc = lfirst_node(AlterTableCmd, l); + + if (atc->subtype == AT_AttachPartition) + { + found = true; + break; + } + } + if (found) + { + /* + * The target table must be a partitioned table, but if, we don't + * need to fail here; The normal ALTER TABLE processing will do that. + */ + if (relkind == RELKIND_PARTITIONED_TABLE) + lock_partition_ancestors(relid, AccessShareLock); + } + } ReleaseSysCache(tuple); } diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index a93cf081dd2..1882de56126 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -14,6 +14,7 @@ #define PARTITION_H #include "partitioning/partdefs.h" +#include "storage/lockdefs.h" #include "utils/relcache.h" /* Seed for the extended hash function */ @@ -21,6 +22,7 @@ extern Oid get_partition_parent(Oid relid, bool even_if_detached); extern List *get_partition_ancestors(Oid relid); +extern List *lock_partition_ancestors(Oid relid, LOCKMODE lockmode); extern Oid index_get_partition(Relation partition, Oid indexId); extern List *map_partition_varattnos(List *expr, int fromrel_varno, Relation to_rel, Relation from_rel); -- 2.39.5 (Apple Git-154)