|
| 1 | +# Test invariants for pg_get_multixact_stats() |
| 2 | +# We create exactly one fresh MultiXact on a brand-new table. While it is pinned |
| 3 | +# by two open transactions, we assert only invariants that background VACUUM/FREEZE |
| 4 | +# cannot violate: |
| 5 | +# • members increased by ≥ 1 when the second session locked the row, |
| 6 | +# • num_mxids / num_members did not decrease vs earlier snapshots, |
| 7 | +# • oldest_* never decreases. |
| 8 | +# We make NO assertions after releasing locks (freezing/truncation may shrink deltas). |
| 9 | +# |
| 10 | +# Terminology (global counters): |
| 11 | +# num_mxids, num_members : "in-use" deltas derived from global horizons |
| 12 | +# oldest_multixact, offset : oldest horizons; they move forward, never backward |
| 13 | +# |
| 14 | +# All assertions execute while our multixact is pinned by open txns, which protects |
| 15 | +# the truncation horizon (VACUUM can't advance past our pinned multi). |
| 16 | + |
| 17 | +setup |
| 18 | +{ |
| 19 | + CREATE TABLE mxq(id int PRIMARY KEY, v int); |
| 20 | + INSERT INTO mxq VALUES (1, 42); |
| 21 | +} |
| 22 | + |
| 23 | +teardown |
| 24 | +{ |
| 25 | + DROP TABLE mxq; |
| 26 | +} |
| 27 | + |
| 28 | +# Two sessions that lock on the same tuple -> one MultiXact with >= 2 members. |
| 29 | +session "s1" |
| 30 | +setup { SET client_min_messages = warning; SET lock_timeout = '5s'; } |
| 31 | +step s1_begin { BEGIN; } |
| 32 | +step s1_lock { SELECT 1 FROM mxq WHERE id=1 FOR KEY SHARE; } |
| 33 | +step s1_commit { COMMIT; } |
| 34 | + |
| 35 | +session "s2" |
| 36 | +setup { SET client_min_messages = warning; SET lock_timeout = '5s'; } |
| 37 | +step s2_begin { BEGIN; } |
| 38 | +step s2_lock { SELECT 1 FROM mxq WHERE id=1 FOR KEY SHARE; } |
| 39 | +step s2_commit { COMMIT; } |
| 40 | + |
| 41 | +# Baseline BEFORE any locking; may be NULLs if multixact isn't initialized yet. |
| 42 | +step snap0 { |
| 43 | + CREATE TEMP TABLE snap0 AS |
| 44 | + SELECT num_mxids, num_members, oldest_multixact |
| 45 | + FROM pg_get_multixact_stats(); |
| 46 | +} |
| 47 | + |
| 48 | +# After s1 has locked the row. |
| 49 | +step snap1 { |
| 50 | + CREATE TEMP TABLE snap1 AS |
| 51 | + SELECT num_mxids, num_members, oldest_multixact |
| 52 | + FROM pg_get_multixact_stats(); |
| 53 | +} |
| 54 | + |
| 55 | +# After s2 joins on the SAME tuple -> multixact with >= 2 members. |
| 56 | +step snap2 { |
| 57 | + CREATE TEMP TABLE snap2 AS |
| 58 | + SELECT num_mxids, num_members, oldest_multixact |
| 59 | + FROM pg_get_multixact_stats(); |
| 60 | +} |
| 61 | + |
| 62 | +# Pretty, deterministic key/value output of boolean checks. |
| 63 | +# Keys: |
| 64 | +# is_init_mxids : num_mxids is non-NULL |
| 65 | +# is_init_members : num_members is non-NULL |
| 66 | +# is_init_oldest_mxid : oldest_multixact is non-NULL |
| 67 | +# is_oldest_mxid_nondec_01 : oldest_multixact did not decrease (snap0→snap1) |
| 68 | +# is_oldest_mxid_nondec_12 : oldest_multixact did not decrease (snap1→snap2) |
| 69 | +# is_members_increased_ge1 : members increased by at least 1 when s2 joined |
| 70 | +# is_mxids_nondec_01 : num_mxids did not decrease (snap0→snap1) |
| 71 | +# is_mxids_nondec_12 : num_mxids did not decrease (snap1→snap2) |
| 72 | +# is_members_nondec_01 : num_members did not decrease (snap0→snap1) |
| 73 | +# is_members_nondec_12 : num_members did not decrease (snap1→snap2) |
| 74 | +step check_while_pinned { |
| 75 | + SELECT r.assertion, r.ok |
| 76 | + FROM snap0 s0 |
| 77 | + JOIN snap1 s1 ON TRUE |
| 78 | + JOIN snap2 s2 ON TRUE, |
| 79 | + LATERAL unnest( |
| 80 | + ARRAY[ |
| 81 | + 'is_init_mxids', |
| 82 | + 'is_init_members', |
| 83 | + 'is_init_oldest_mxid', |
| 84 | + 'is_init_oldest_off', |
| 85 | + 'is_oldest_mxid_nondec_01', |
| 86 | + 'is_oldest_mxid_nondec_12', |
| 87 | + 'is_oldest_off_nondec_01', |
| 88 | + 'is_oldest_off_nondec_12', |
| 89 | + 'is_members_increased_ge1', |
| 90 | + 'is_mxids_nondec_01', |
| 91 | + 'is_mxids_nondec_12', |
| 92 | + 'is_members_nondec_01', |
| 93 | + 'is_members_nondec_12' |
| 94 | + ], |
| 95 | + ARRAY[ |
| 96 | + (s2.num_mxids IS NOT NULL), |
| 97 | + (s2.num_members IS NOT NULL), |
| 98 | + (s2.oldest_multixact IS NOT NULL), |
| 99 | + |
| 100 | + (s1.oldest_multixact::text::bigint >= COALESCE(s0.oldest_multixact::text::bigint, 0)), |
| 101 | + (s2.oldest_multixact::text::bigint >= COALESCE(s1.oldest_multixact::text::bigint, 0)), |
| 102 | + |
| 103 | + (s2.num_members >= COALESCE(s1.num_members, 0) + 1), |
| 104 | + |
| 105 | + (s1.num_mxids >= COALESCE(s0.num_mxids, 0)), |
| 106 | + (s2.num_mxids >= COALESCE(s1.num_mxids, 0)), |
| 107 | + (s1.num_members >= COALESCE(s0.num_members, 0)), |
| 108 | + (s2.num_members >= COALESCE(s1.num_members, 0)) |
| 109 | + ] |
| 110 | + ) AS r(assertion, ok); |
| 111 | +} |
| 112 | + |
| 113 | +permutation snap0 s1_begin s1_lock snap1 s2_begin s2_lock snap2 check_while_pinned s1_commit s2_commit |
0 commit comments