From efea79f4f9459da747c56a2775646e41e91f502c Mon Sep 17 00:00:00 2001 From: Chee Wooson Date: Tue, 15 Sep 2026 14:21:51 +0800 Subject: [PATCH v1 1/2] Discard aborted updaters in MultiXactIdExpand() An updater can be marked aborted in pg_xact while still in ProcArray. DoesMultiXactIdConflict() allows another update in this window, but MultiXactIdExpand() retains the old updater because it appears running. Adding the new updater then fails with "new multixact has more than one updating member". Discard explicitly aborted updaters, matching DoesMultiXactIdConflict(). Keep the existing liveness checks to also discard crashed updaters that have no abort record in pg_xact. --- src/backend/access/transam/multixact.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index d688815083c..0f40f09b890 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -486,6 +486,16 @@ MultiXactIdExpand(MultiXactId multi, TransactionId xid, MultiXactStatus status) for (i = 0, j = 0; i < nmembers; i++) { + /* + * An updater can be marked aborted in pg_xact before leaving + * ProcArray. Discard it even if it appears running, since a new + * update need not wait for it and must not create a multixact with + * two updating members. + */ + if (ISUPDATE_from_mxstatus(members[i].status) && + TransactionIdDidAbort(members[i].xid)) + continue; + if (TransactionIdIsInProgress(members[i].xid) || (ISUPDATE_from_mxstatus(members[i].status) && TransactionIdDidCommit(members[i].xid))) -- 2.43.0