From dfcf566a3ffe8b690e7f5669ead4f7de17a125d5 Mon Sep 17 00:00:00 2001 From: Chee Wooson Date: Tue, 15 Sep 2026 14:21:54 +0800 Subject: [PATCH v1 2/2] Test multixact expansion during updater abort Add an injection point after recording an abort in pg_xact and before ProcArray cleanup. Pause an updater there after a key-share lock has made the tuple's xmax a multixact, then verify that another update succeeds without retaining the aborted updater. --- src/backend/access/transam/xact.c | 7 +++ src/test/modules/injection_points/Makefile | 3 +- .../expected/multixact-aborted-updater.out | 28 +++++++++ src/test/modules/injection_points/meson.build | 1 + .../specs/multixact-aborted-updater.spec | 59 +++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/test/modules/injection_points/expected/multixact-aborted-updater.out create mode 100644 src/test/modules/injection_points/specs/multixact-aborted-updater.spec diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index aca92507ebd..9fbf1212493 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -65,6 +65,7 @@ #include "utils/builtins.h" #include "utils/combocid.h" #include "utils/guc.h" +#include "utils/injection_point.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/relmapper.h" @@ -1915,6 +1916,12 @@ RecordTransactionAbort(bool isSubXact) if (ndroppedstats) pfree(droppedstats); + /* + * Test the window where the transaction is aborted in pg_xact but still + * present in ProcArray. + */ + INJECTION_POINT("transaction-abort-after-clog", NULL); + return latestXid; } diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index 408a35c3c21..6c4d2e00647 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -26,7 +26,8 @@ ISOLATION = basic \ ri_fastpath_snapshot \ syscache-update-pruned \ wait_cleanup \ - heap_lock_update + heap_lock_update \ + multixact-aborted-updater # some isolation tests require wal_level=replica ISOLATION_OPTS = --temp-config $(top_srcdir)/src/test/modules/injection_points/extra.conf diff --git a/src/test/modules/injection_points/expected/multixact-aborted-updater.out b/src/test/modules/injection_points/expected/multixact-aborted-updater.out new file mode 100644 index 00000000000..de5f5bb94dc --- /dev/null +++ b/src/test/modules/injection_points/expected/multixact-aborted-updater.out @@ -0,0 +1,28 @@ +Parsed test spec with 3 sessions + +starting permutation: s1begin s1update s2lock s1abort s3update wake +step s1begin: BEGIN; +step s1update: UPDATE mxact_abort SET filler = 's1' WHERE id = 1; +step s2lock: SELECT * FROM mxact_abort WHERE id = 1 FOR KEY SHARE; +id|filler +--+------- + 1|initial +(1 row) + +step s1abort: ROLLBACK; +s3: NOTICE: session 3 update succeeded +step s3update: + DO $$ + BEGIN + UPDATE mxact_abort SET filler = 's3' WHERE id = 1; + RAISE NOTICE 'session 3 update succeeded'; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'session 3 update failed: %', split_part(SQLERRM, ':', 1); + END + $$; + +step wake: + SELECT FROM injection_points_detach('transaction-abort-after-clog'); + SELECT FROM injection_points_wakeup('transaction-abort-after-clog'); + +step s1abort: <... completed> diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index a7b40e084f6..f506f4a2a44 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -56,6 +56,7 @@ tests += { 'syscache-update-pruned', 'wait_cleanup', 'heap_lock_update', + 'multixact-aborted-updater', ], 'runningcheck': false, # see syscache-update-pruned # Some tests wait for all snapshots, so avoid parallel execution diff --git a/src/test/modules/injection_points/specs/multixact-aborted-updater.spec b/src/test/modules/injection_points/specs/multixact-aborted-updater.spec new file mode 100644 index 00000000000..4935aaa1f5f --- /dev/null +++ b/src/test/modules/injection_points/specs/multixact-aborted-updater.spec @@ -0,0 +1,59 @@ +# Test multixact expansion with an aborted updater still in ProcArray. +# +# Pause session 1 after recording its abort in pg_xact, before ProcArray cleanup. +# Session 3 can update without waiting, but expanding the multixact must discard +# session 1 to avoid having two updating members. + +setup +{ + CREATE EXTENSION injection_points; + + CREATE TABLE mxact_abort (id int PRIMARY KEY, filler text); + INSERT INTO mxact_abort VALUES (1, 'initial'); +} + +teardown +{ + DROP TABLE mxact_abort; + DROP EXTENSION injection_points; +} + +session s1 +setup { + SELECT FROM injection_points_set_local(); + SELECT FROM injection_points_attach('transaction-abort-after-clog', 'wait'); +} +step s1begin { BEGIN; } +step s1update { UPDATE mxact_abort SET filler = 's1' WHERE id = 1; } +step s1abort { ROLLBACK; } + +session s2 +# The compatible key-share lock makes xmax a multixact with session 1 as updater. +step s2lock { SELECT * FROM mxact_abort WHERE id = 1 FOR KEY SHARE; } + +session s3 +# Omit variable XIDs from the error message. +step s3update { + DO $$ + BEGIN + UPDATE mxact_abort SET filler = 's3' WHERE id = 1; + RAISE NOTICE 'session 3 update succeeded'; + EXCEPTION WHEN OTHERS THEN + RAISE NOTICE 'session 3 update failed: %', split_part(SQLERRM, ':', 1); + END + $$; +} +step wake { + SELECT FROM injection_points_detach('transaction-abort-after-clog'); + SELECT FROM injection_points_wakeup('transaction-abort-after-clog'); +} + +permutation + s1begin + s1update + s2lock + # Pause after recording the abort, before ProcArray cleanup. + s1abort + # Expand the multixact without retaining the aborted updater. + s3update + wake -- 2.43.0