From c54b0d27c3784a1e2e56c91a871ee76e39f0b4c2 Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Mon, 28 Sep 2026 15:29:05 +0200 Subject: [PATCH] Recheck partition index attachment after locking it When creating an index on a partitioned table, DefineIndex() may reuse an existing index on a partition that is not yet attached to a parent. It checked that with has_superclass() before locking the child index. If ALTER INDEX ... ATTACH PARTITION committed while CREATE INDEX waited for that lock, CREATE INDEX then tried to attach the index to a second parent and failed with "bogus pg_inherit row". Repeat the check after acquiring the lock. Keep the unlocked check as well, so that already-attached indexes are skipped without locking them; otherwise CREATE INDEX could block, or deadlock, on locks held on indexes it has no use for. Add an isolation test covering both cases. Reported-by: Alexander Lakhin Author: Ayush Tiwari Reviewed-by: Rafia Sabih Bug: #19723 Backpatch-through: 14 --- src/backend/commands/indexcmds.c | 16 +++++++ .../expected/partition-index-attach.out | 45 ++++++++++++++++++ src/test/isolation/isolation_schedule | 1 + .../specs/partition-index-attach.spec | 46 +++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/test/isolation/expected/partition-index-attach.out create mode 100644 src/test/isolation/specs/partition-index-attach.spec diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 5a0312fe772..c5f81d3e1f0 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -1458,6 +1458,22 @@ DefineIndex(ParseState *pstate, continue; cldidx = index_open(cldidxid, lockmode); + + /* + * Recheck now that we hold the lock, in case the index + * was concurrently attached to some other parent while we + * waited for it. The unlocked check above cannot give a + * stale true answer, because the lock we hold on the + * partition prevents detaching it or dropping the parent + * index; so it only serves to avoid locking indexes we're + * going to skip anyway. + */ + if (has_superclass(cldidxid)) + { + index_close(cldidx, lockmode); + continue; + } + cldIdxInfo = BuildIndexInfo(cldidx); if (CompareIndexInfo(cldIdxInfo, indexInfo, cldidx->rd_indcollation, diff --git a/src/test/isolation/expected/partition-index-attach.out b/src/test/isolation/expected/partition-index-attach.out new file mode 100644 index 00000000000..3bb6ad3054b --- /dev/null +++ b/src/test/isolation/expected/partition-index-attach.out @@ -0,0 +1,45 @@ +Parsed test spec with 2 sessions + +starting permutation: s1b s1attach s2create s1c s2check +step s1b: BEGIN; +step s1attach: ALTER INDEX pia_old ATTACH PARTITION pia_1_a; +step s2create: CREATE INDEX pia_new ON pia (a); +step s1c: COMMIT; +step s2create: <... completed> +step s2check: + SELECT c.relname AS index, p.relname AS parent + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhrelid + JOIN pg_class p ON p.oid = i.inhparent + WHERE p.relname IN ('pia_old', 'pia_new') + ORDER BY 1; + +index |parent +-----------+------- +pia_1_a |pia_old +pia_1_a_idx|pia_new +pia_2_a |pia_old +pia_2_a_idx|pia_new +(4 rows) + + +starting permutation: s1b s1rename s2create s1c s2check +step s1b: BEGIN; +step s1rename: ALTER INDEX pia_2_a RENAME TO pia_2_renamed; +step s2create: CREATE INDEX pia_new ON pia (a); +step s1c: COMMIT; +step s2check: + SELECT c.relname AS index, p.relname AS parent + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhrelid + JOIN pg_class p ON p.oid = i.inhparent + WHERE p.relname IN ('pia_old', 'pia_new') + ORDER BY 1; + +index |parent +-------------+------- +pia_1_a |pia_new +pia_2_a_idx |pia_new +pia_2_renamed|pia_old +(3 rows) + diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 8470d50d2bc..285184413a6 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -113,6 +113,7 @@ test: predicate-gist test: predicate-gin test: partition-concurrent-attach test: partition-drop-index-locking +test: partition-index-attach test: partition-key-update-1 test: partition-key-update-2 test: partition-key-update-3 diff --git a/src/test/isolation/specs/partition-index-attach.spec b/src/test/isolation/specs/partition-index-attach.spec new file mode 100644 index 00000000000..093226b736b --- /dev/null +++ b/src/test/isolation/specs/partition-index-attach.spec @@ -0,0 +1,46 @@ +# Test CREATE INDEX on a partitioned table concurrently with +# ALTER INDEX ... ATTACH PARTITION on one of its partitions' indexes. +# +# CREATE INDEX must notice that the partition's index was attached to +# another parent while it waited for the lock on it, and must not wait for +# locks on partition indexes that were already attached. + +setup +{ + CREATE TABLE pia (a int) PARTITION BY RANGE (a); + CREATE TABLE pia_1 PARTITION OF pia FOR VALUES FROM (0) TO (10); + CREATE TABLE pia_2 PARTITION OF pia FOR VALUES FROM (10) TO (20); + CREATE INDEX pia_old ON ONLY pia (a); + CREATE INDEX pia_1_a ON pia_1 (a); + CREATE INDEX pia_2_a ON pia_2 (a); + ALTER INDEX pia_old ATTACH PARTITION pia_2_a; +} + +teardown +{ + DROP TABLE pia; +} + +session s1 +step s1b { BEGIN; } +step s1attach { ALTER INDEX pia_old ATTACH PARTITION pia_1_a; } +step s1rename { ALTER INDEX pia_2_a RENAME TO pia_2_renamed; } +step s1c { COMMIT; } + +session s2 +step s2create { CREATE INDEX pia_new ON pia (a); } +step s2check { + SELECT c.relname AS index, p.relname AS parent + FROM pg_inherits i + JOIN pg_class c ON c.oid = i.inhrelid + JOIN pg_class p ON p.oid = i.inhparent + WHERE p.relname IN ('pia_old', 'pia_new') + ORDER BY 1; +} + +# CREATE INDEX waits for the ATTACH, then must build a new index on pia_1 +# rather than trying to reuse pia_1_a. +permutation s1b s1attach s2create s1c s2check + +# CREATE INDEX must not wait for the lock on an already-attached index. +permutation s1b s1rename s2create s1c s2check -- 2.39.5 (Apple Git-154)