From 28ef6c33f7a110fe976821573072f035193b4a01 Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Sun, 23 Aug 2026 15:20:16 +0300 Subject: [PATCH 3/4] Test SSI conflict detection after summarization When SSI exhausts its transaction array, it preserves old SIREAD locks by folding them into OldCommittedSxact. Conflict detection must continue to prevent write skew after that transition, but no automated test forces this path. Add an injection point that summarizes the oldest committed serializable transaction, and use it to exercise the same write-skew schedule before and after summarization. --- src/backend/storage/lmgr/predicate.c | 8 ++ src/test/modules/injection_points/Makefile | 1 + .../expected/serializable-summarization.out | 98 +++++++++++++++++++ src/test/modules/injection_points/meson.build | 1 + .../specs/serializable-summarization.spec | 70 +++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 src/test/modules/injection_points/expected/serializable-summarization.out create mode 100644 src/test/modules/injection_points/specs/serializable-summarization.spec diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 457a51fecc6..666c2dfff18 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -210,6 +210,7 @@ #include "storage/shmem.h" #include "storage/subsystems.h" #include "utils/guc_hooks.h" +#include "utils/injection_point.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/wait_event.h" @@ -1786,6 +1787,13 @@ GetSerializableTransactionSnapshotInt(Snapshot snapshot, */ #ifdef TEST_SUMMARIZE_SERIAL SummarizeOldestCommittedSxact(); +#endif +#ifdef USE_INJECTION_POINTS + if (IS_INJECTION_POINT_ATTACHED("serializable-summarize")) + { + INJECTION_POINT("serializable-summarize", NULL); + SummarizeOldestCommittedSxact(); + } #endif LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); do diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index 2463d6e0fb9..f67fe49cd41 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -32,6 +32,7 @@ ISOLATION = basic \ repack_toast \ ri_fastpath_reindex \ ri_fastpath_snapshot \ + serializable-summarization \ syscache-update-pruned \ $(WAIT_CLEANUP_TST) \ heap_lock_update \ diff --git a/src/test/modules/injection_points/expected/serializable-summarization.out b/src/test/modules/injection_points/expected/serializable-summarization.out new file mode 100644 index 00000000000..0604ac670ed --- /dev/null +++ b/src/test/modules/injection_points/expected/serializable-summarization.out @@ -0,0 +1,98 @@ +Parsed test spec with 3 sessions + +starting permutation: s1_begin s1_read s2_begin s2_read s2_write s2_commit s1_write s1_commit +step s1_begin: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1_read: SELECT count(*) FROM summarized_write_skew WHERE v = 1; +count +----- + 0 +(1 row) + +step s2_begin: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2_read: SELECT count(*) FROM summarized_write_skew WHERE v = 1; +count +----- + 0 +(1 row) + +step s2_write: + UPDATE summarized_write_skew + SET v = 1 + WHERE id = 1 + AND (SELECT count(*) FROM summarized_write_skew WHERE v = 1) = 0; + +step s2_commit: COMMIT; +step s1_write: + UPDATE summarized_write_skew + SET v = 1 + WHERE id = 2 + AND (SELECT count(*) FROM summarized_write_skew WHERE v = 1) = 0; + +ERROR: could not serialize access due to read/write dependencies among transactions +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_read s2_begin s2_read s2_write s2_commit attach summarize detach s1_write s1_commit +step s1_begin: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1_read: SELECT count(*) FROM summarized_write_skew WHERE v = 1; +count +----- + 0 +(1 row) + +step s2_begin: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s2_read: SELECT count(*) FROM summarized_write_skew WHERE v = 1; +count +----- + 0 +(1 row) + +step s2_write: + UPDATE summarized_write_skew + SET v = 1 + WHERE id = 1 + AND (SELECT count(*) FROM summarized_write_skew WHERE v = 1) = 0; + +step s2_commit: COMMIT; +step attach: + SELECT injection_points_attach('serializable-summarize', 'notice'); + +injection_points_attach +----------------------- + +(1 row) + +s3: NOTICE: notice triggered for injection point serializable-summarize +step summarize: + BEGIN ISOLATION LEVEL SERIALIZABLE; + SELECT 1; + COMMIT; + +?column? +-------- + 1 +(1 row) + +step detach: + SELECT count(*) > 0 AS locks_summarized + FROM pg_locks + WHERE mode = 'SIReadLock' AND virtualtransaction = '-1/0'; + SELECT injection_points_detach('serializable-summarize'); + +locks_summarized +---------------- +t +(1 row) + +injection_points_detach +----------------------- + +(1 row) + +step s1_write: + UPDATE summarized_write_skew + SET v = 1 + WHERE id = 2 + AND (SELECT count(*) FROM summarized_write_skew WHERE v = 1) = 0; + +ERROR: could not serialize access due to read/write dependencies among transactions +step s1_commit: COMMIT; diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index b4a0079484b..955e28e25a6 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -40,6 +40,7 @@ injection_points_isolation = [ 'repack_toast', 'ri_fastpath_reindex', 'ri_fastpath_snapshot', + 'serializable-summarization', 'syscache-update-pruned', 'heap_lock_update', 'on_conflict_probe_window', diff --git a/src/test/modules/injection_points/specs/serializable-summarization.spec b/src/test/modules/injection_points/specs/serializable-summarization.spec new file mode 100644 index 00000000000..2bc72184daf --- /dev/null +++ b/src/test/modules/injection_points/specs/serializable-summarization.spec @@ -0,0 +1,70 @@ +# Test SSI conflict detection after summarization. + +setup +{ + CREATE EXTENSION injection_points; + CREATE TABLE summarized_write_skew (id int PRIMARY KEY, v int); + INSERT INTO summarized_write_skew VALUES (1, 0), (2, 0); +} + +teardown +{ + DROP TABLE summarized_write_skew; + DROP EXTENSION injection_points; +} + +session s1 +step s1_begin { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step s1_read { SELECT count(*) FROM summarized_write_skew WHERE v = 1; } +step s1_write +{ + UPDATE summarized_write_skew + SET v = 1 + WHERE id = 2 + AND (SELECT count(*) FROM summarized_write_skew WHERE v = 1) = 0; +} +step s1_commit { COMMIT; } + +session s2 +step s2_begin { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step s2_read { SELECT count(*) FROM summarized_write_skew WHERE v = 1; } +step s2_write +{ + UPDATE summarized_write_skew + SET v = 1 + WHERE id = 1 + AND (SELECT count(*) FROM summarized_write_skew WHERE v = 1) = 0; +} +step s2_commit { COMMIT; } + +session s3 +step attach +{ + SELECT injection_points_attach('serializable-summarize', 'notice'); +} +step summarize +{ + BEGIN ISOLATION LEVEL SERIALIZABLE; + SELECT 1; + COMMIT; +} +step detach +{ + SELECT count(*) > 0 AS locks_summarized + FROM pg_locks + WHERE mode = 'SIReadLock' AND virtualtransaction = '-1/0'; + SELECT injection_points_detach('serializable-summarize'); +} + +# The ordinary, unsummarized conflict is a control case. +permutation + s1_begin s1_read + s2_begin s2_read s2_write s2_commit + s1_write s1_commit + +# Moving s2's SIREAD locks to OldCommittedSxact must preserve the conflict. +permutation + s1_begin s1_read + s2_begin s2_read s2_write s2_commit + attach summarize detach + s1_write s1_commit -- 2.55.0