|
| 1 | +# Test race conditions involving: |
| 2 | +# - s1: VACUUM inplace-updating a pg_class row |
| 3 | +# - s2: GRANT/REVOKE making pg_class rows dead |
| 4 | +# - s3: "VACUUM pg_class" making dead rows LP_UNUSED; DDL reusing them |
| 5 | + |
| 6 | +# Need GRANT to make a non-HOT update. Otherwise, "VACUUM pg_class" would |
| 7 | +# leave an LP_REDIRECT that persists. To get non-HOT, make rels so the |
| 8 | +# pg_class row for vactest.orig50 is on a filled page (assuming BLCKSZ=8192). |
| 9 | +# Just to save on filesystem syscalls, use relkind=c for every other rel. |
| 10 | +setup |
| 11 | +{ |
| 12 | + CREATE EXTENSION injection_points; |
| 13 | + CREATE SCHEMA vactest; |
| 14 | + CREATE FUNCTION vactest.mkrels(text, int, int) RETURNS void |
| 15 | + LANGUAGE plpgsql SET search_path = vactest AS $$ |
| 16 | + DECLARE |
| 17 | + tname text; |
| 18 | + BEGIN |
| 19 | + FOR i in $2 .. $3 LOOP |
| 20 | + tname := $1 || i; |
| 21 | + EXECUTE FORMAT('CREATE TYPE ' || tname || ' AS ()'); |
| 22 | + RAISE DEBUG '% at %', tname, ctid |
| 23 | + FROM pg_class WHERE oid = tname::regclass; |
| 24 | + END LOOP; |
| 25 | + END |
| 26 | + $$; |
| 27 | +} |
| 28 | +setup { VACUUM FULL pg_class; -- reduce free space } |
| 29 | +setup |
| 30 | +{ |
| 31 | + SELECT vactest.mkrels('orig', 1, 49); |
| 32 | + CREATE TABLE vactest.orig50 (); |
| 33 | + SELECT vactest.mkrels('orig', 51, 100); |
| 34 | +} |
| 35 | + |
| 36 | +# XXX DROP causes an assertion failure; adopt DROP once fixed |
| 37 | +teardown |
| 38 | +{ |
| 39 | + --DROP SCHEMA vactest CASCADE; |
| 40 | + DO $$BEGIN EXECUTE 'ALTER SCHEMA vactest RENAME TO schema' || oid FROM pg_namespace where nspname = 'vactest'; END$$; |
| 41 | + DROP EXTENSION injection_points; |
| 42 | +} |
| 43 | + |
| 44 | +# Wait during inplace update, in a VACUUM of vactest.orig50. |
| 45 | +session s1 |
| 46 | +setup { |
| 47 | + SELECT injection_points_set_local(); |
| 48 | + SELECT injection_points_attach('inplace-before-pin', 'wait'); |
| 49 | +} |
| 50 | +step vac1 { VACUUM vactest.orig50; -- wait during inplace update } |
| 51 | +# One bug scenario leaves two live pg_class tuples for vactest.orig50 and zero |
| 52 | +# live tuples for one of the "intruder" rels. REINDEX observes the duplicate. |
| 53 | +step read1 { |
| 54 | + REINDEX TABLE pg_class; -- look for duplicates |
| 55 | + SELECT reltuples = -1 AS reltuples_unknown |
| 56 | + FROM pg_class WHERE oid = 'vactest.orig50'::regclass; |
| 57 | +} |
| 58 | + |
| 59 | + |
| 60 | +# Transactional updates of the tuple vac1 is waiting to inplace-update. |
| 61 | +session s2 |
| 62 | +step grant2 { GRANT SELECT ON TABLE vactest.orig50 TO PUBLIC; } |
| 63 | + |
| 64 | + |
| 65 | +# Non-blocking actions. |
| 66 | +session s3 |
| 67 | +step vac3 { VACUUM pg_class; } |
| 68 | +# Reuse the lp that vac1 is waiting to change. I've observed reuse at the 1st |
| 69 | +# or 18th CREATE, so create excess. |
| 70 | +step mkrels3 { |
| 71 | + SELECT vactest.mkrels('intruder', 1, 100); -- repopulate LP_UNUSED |
| 72 | + SELECT injection_points_detach('inplace-before-pin'); |
| 73 | + SELECT injection_points_wakeup('inplace-before-pin'); |
| 74 | +} |
| 75 | + |
| 76 | + |
| 77 | +# XXX extant bug |
| 78 | +permutation |
| 79 | + vac1(mkrels3) # reads pg_class tuple T0 for vactest.orig50, xmax invalid |
| 80 | + grant2 # T0 becomes eligible for pruning, T1 is successor |
| 81 | + vac3 # T0 becomes LP_UNUSED |
| 82 | + mkrels3 # T0 reused; vac1 wakes and overwrites the reused T0 |
| 83 | + read1 |
0 commit comments