From 5f0e1dce5974c48eaecb855d4676e497abfc0b1a Mon Sep 17 00:00:00 2001 From: Andrey Borodin Date: Sat, 30 May 2026 23:07:39 +0500 Subject: [PATCH v1 1/2] Add isolation test: SSI serialization failure swallowed by SAVEPOINT If that error is raised inside a subtransaction, ROLLBACK TO SAVEPOINT swallows it and the transaction is free to COMMIT, defeating serializable isolation. --- .../expected/serializable-savepoint.out | 25 +++++++++ src/test/isolation/isolation_schedule | 2 + .../specs/serializable-savepoint.spec | 52 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 src/test/isolation/expected/serializable-savepoint.out create mode 100644 src/test/isolation/specs/serializable-savepoint.spec diff --git a/src/test/isolation/expected/serializable-savepoint.out b/src/test/isolation/expected/serializable-savepoint.out new file mode 100644 index 00000000000..0dd4f787efd --- /dev/null +++ b/src/test/isolation/expected/serializable-savepoint.out @@ -0,0 +1,25 @@ +Parsed test spec with 3 sessions + +starting permutation: r1 w2 w1 c1 sp2 r2 rb2 c2 rall +step r1: SELECT v FROM t WHERE id = 2; +v +- +0 +(1 row) + +step w2: UPDATE t SET v = 1 WHERE id = 2; +step w1: UPDATE t SET v = 1 WHERE id = 1; +step c1: COMMIT; +step sp2: SAVEPOINT f; +step r2: SELECT v FROM t WHERE id = 1; +ERROR: could not serialize access due to read/write dependencies among transactions +step rb2: ROLLBACK TO SAVEPOINT f; +step c2: COMMIT; +ERROR: could not serialize access due to read/write dependencies among transactions +step rall: SELECT id, v FROM t ORDER BY id; +id|v +--+- + 1|1 + 2|0 +(2 rows) + diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 15c33fad4c5..62a334ad3d9 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -127,3 +127,5 @@ test: matview-write-skew test: lock-nowait test: for-portion-of test: ddl-dependency-locking + +test: serializable-savepoint diff --git a/src/test/isolation/specs/serializable-savepoint.spec b/src/test/isolation/specs/serializable-savepoint.spec new file mode 100644 index 00000000000..de61b615529 --- /dev/null +++ b/src/test/isolation/specs/serializable-savepoint.spec @@ -0,0 +1,52 @@ +# Test that a serialization failure raised while *checking* a read (the +# "conflict out to pivot, during read" cancellation) cannot be discarded by +# rolling back to a SAVEPOINT. +# +# s1 and s2 form the classic write-skew dangerous structure under SERIALIZABLE: +# s1 reads row 2 and writes row 1 +# s2 writes row 2 and reads row 1 +# s1 commits first. When s2 then reads row 1 it is correctly identified as the +# pivot of a dangerous structure and PostgreSQL raises +# ERROR: could not serialize access ... +# (Canceled on conflict out to pivot ..., during read). +# +# Crucially, s2's *write* to row 2 happened BEFORE the savepoint, so it is not +# undone. s2 only wraps the offending READ in a SAVEPOINT, rolls back to it +# (swallowing the error) and commits. That COMMIT must fail: allowing it +# leaves both s1's and s2's writes committed, which is the write-skew anomaly +# SSI is supposed to prevent (no serial order exists). + +setup +{ + CREATE TABLE t (id int PRIMARY KEY, v int); + INSERT INTO t VALUES (1, 0), (2, 0); +} + +teardown +{ + DROP TABLE t; +} + +session s1 +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step r1 { SELECT v FROM t WHERE id = 2; } +step w1 { UPDATE t SET v = 1 WHERE id = 1; } +step c1 { COMMIT; } + +session s2 +setup { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step w2 { UPDATE t SET v = 1 WHERE id = 2; } +step sp2 { SAVEPOINT f; } +step r2 { SELECT v FROM t WHERE id = 1; } +step rb2 { ROLLBACK TO SAVEPOINT f; } +step c2 { COMMIT; } + +# Used to observe the final committed state. +session s3 +step rall { SELECT id, v FROM t ORDER BY id; } + +# s2 takes its snapshot at w2 (before s1 commits), writes row 2, then after s1 +# commits it reads row 1 inside a savepoint and is cancelled. After rolling +# back to the savepoint it must not be able to commit. If it does (the bug), +# rall shows both rows updated -- the non-serializable write-skew outcome. +permutation r1 w2 w1 c1 sp2 r2 rb2 c2 rall -- 2.50.1 (Apple Git-155)