Hi, An updating member of a multixact can be marked aborted in pg_xact while its backend is still present in ProcArray. A concurrent update need not wait for that member, but MultiXactIdExpand() currently retains it because TransactionIdIsInProgress() still returns true. Adding the concurrent updater then fails with:
ERROR: new multixact has more than one updating member
The relevant interleaving is:
1. One transaction updates a row.
2. A second transaction takes a compatible key-share lock, changing the
row's xmax into a multixact containing the updater and locker.
3. The updater records its abort in pg_xact, but has not yet been removed
from ProcArray.
4. A third transaction updates the row. The conflict check ignores the
aborted updater, but multixact expansion retains it as apparently
running and tries to add another updater.
The first patch discards an updating member when pg_xact already records
its abort, even if it still appears to be running. An abort status is
final. The existing checks for running and committed members remain in
place, so an updater whose backend crashed without recording an abort is
still discarded.
I reproduced this problem with a TPC-C test. The same failure was reported
in the 2022 thread "Fix for visibility check on 14.5 fails on tpcc with high
concurrency":
https://postgr.es/m/co2pr0801mb2310579f65529380a4e5edc0e2...@co2pr0801mb2310.namprd08.prod.outlook.com
That report showed the same error and WAL records for an updater that
aborted before the next update.
Patch 0002 adds an injection point and an isolation test that reproduce
the race deterministically.
Regards,
Chee Wooson
v1-0001-Discard-aborted-updaters-in-MultiXactIdExpand.patch
Description: Binary data
v1-0002-Test-multixact-expansion-during-updater-abort.patch
Description: Binary data
