mirror of https://github.com/postgres/postgres
Make the isolation harness recognize injection_points wait events as a type of blocked state. Test an extant inplace-update bug. Reviewed by Robert Haas and Michael Paquier. Discussion: https://postgr.es/m/20240512232923.aa.nmisch@google.compull/165/head
parent
abfbd13af0
commit
c35f419d6e
@ -0,0 +1,43 @@ |
|||||||
|
Parsed test spec with 3 sessions |
||||||
|
|
||||||
|
starting permutation: vac1 grant2 vac3 mkrels3 read1 |
||||||
|
mkrels |
||||||
|
------ |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
injection_points_attach |
||||||
|
----------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
step vac1: VACUUM vactest.orig50; -- wait during inplace update <waiting ...> |
||||||
|
step grant2: GRANT SELECT ON TABLE vactest.orig50 TO PUBLIC; |
||||||
|
step vac3: VACUUM pg_class; |
||||||
|
step mkrels3: |
||||||
|
SELECT vactest.mkrels('intruder', 1, 100); -- repopulate LP_UNUSED |
||||||
|
SELECT injection_points_detach('inplace-before-pin'); |
||||||
|
SELECT injection_points_wakeup('inplace-before-pin'); |
||||||
|
|
||||||
|
mkrels |
||||||
|
------ |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
injection_points_detach |
||||||
|
----------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
injection_points_wakeup |
||||||
|
----------------------- |
||||||
|
|
||||||
|
(1 row) |
||||||
|
|
||||||
|
step vac1: <... completed> |
||||||
|
step read1: |
||||||
|
REINDEX TABLE pg_class; -- look for duplicates |
||||||
|
SELECT reltuples = -1 AS reltuples_unknown |
||||||
|
FROM pg_class WHERE oid = 'vactest.orig50'::regclass; |
||||||
|
|
||||||
|
ERROR: could not create unique index "pg_class_oid_index" |
@ -0,0 +1,83 @@ |
|||||||
|
# Test race conditions involving: |
||||||
|
# - s1: VACUUM inplace-updating a pg_class row |
||||||
|
# - s2: GRANT/REVOKE making pg_class rows dead |
||||||
|
# - s3: "VACUUM pg_class" making dead rows LP_UNUSED; DDL reusing them |
||||||
|
|
||||||
|
# Need GRANT to make a non-HOT update. Otherwise, "VACUUM pg_class" would |
||||||
|
# leave an LP_REDIRECT that persists. To get non-HOT, make rels so the |
||||||
|
# pg_class row for vactest.orig50 is on a filled page (assuming BLCKSZ=8192). |
||||||
|
# Just to save on filesystem syscalls, use relkind=c for every other rel. |
||||||
|
setup |
||||||
|
{ |
||||||
|
CREATE EXTENSION injection_points; |
||||||
|
CREATE SCHEMA vactest; |
||||||
|
CREATE FUNCTION vactest.mkrels(text, int, int) RETURNS void |
||||||
|
LANGUAGE plpgsql SET search_path = vactest AS $$ |
||||||
|
DECLARE |
||||||
|
tname text; |
||||||
|
BEGIN |
||||||
|
FOR i in $2 .. $3 LOOP |
||||||
|
tname := $1 || i; |
||||||
|
EXECUTE FORMAT('CREATE TYPE ' || tname || ' AS ()'); |
||||||
|
RAISE DEBUG '% at %', tname, ctid |
||||||
|
FROM pg_class WHERE oid = tname::regclass; |
||||||
|
END LOOP; |
||||||
|
END |
||||||
|
$$; |
||||||
|
} |
||||||
|
setup { VACUUM FULL pg_class; -- reduce free space } |
||||||
|
setup |
||||||
|
{ |
||||||
|
SELECT vactest.mkrels('orig', 1, 49); |
||||||
|
CREATE TABLE vactest.orig50 (); |
||||||
|
SELECT vactest.mkrels('orig', 51, 100); |
||||||
|
} |
||||||
|
|
||||||
|
# XXX DROP causes an assertion failure; adopt DROP once fixed |
||||||
|
teardown |
||||||
|
{ |
||||||
|
--DROP SCHEMA vactest CASCADE; |
||||||
|
DO $$BEGIN EXECUTE 'ALTER SCHEMA vactest RENAME TO schema' || oid FROM pg_namespace where nspname = 'vactest'; END$$; |
||||||
|
DROP EXTENSION injection_points; |
||||||
|
} |
||||||
|
|
||||||
|
# Wait during inplace update, in a VACUUM of vactest.orig50. |
||||||
|
session s1 |
||||||
|
setup { |
||||||
|
SELECT injection_points_set_local(); |
||||||
|
SELECT injection_points_attach('inplace-before-pin', 'wait'); |
||||||
|
} |
||||||
|
step vac1 { VACUUM vactest.orig50; -- wait during inplace update } |
||||||
|
# One bug scenario leaves two live pg_class tuples for vactest.orig50 and zero |
||||||
|
# live tuples for one of the "intruder" rels. REINDEX observes the duplicate. |
||||||
|
step read1 { |
||||||
|
REINDEX TABLE pg_class; -- look for duplicates |
||||||
|
SELECT reltuples = -1 AS reltuples_unknown |
||||||
|
FROM pg_class WHERE oid = 'vactest.orig50'::regclass; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# Transactional updates of the tuple vac1 is waiting to inplace-update. |
||||||
|
session s2 |
||||||
|
step grant2 { GRANT SELECT ON TABLE vactest.orig50 TO PUBLIC; } |
||||||
|
|
||||||
|
|
||||||
|
# Non-blocking actions. |
||||||
|
session s3 |
||||||
|
step vac3 { VACUUM pg_class; } |
||||||
|
# Reuse the lp that vac1 is waiting to change. I've observed reuse at the 1st |
||||||
|
# or 18th CREATE, so create excess. |
||||||
|
step mkrels3 { |
||||||
|
SELECT vactest.mkrels('intruder', 1, 100); -- repopulate LP_UNUSED |
||||||
|
SELECT injection_points_detach('inplace-before-pin'); |
||||||
|
SELECT injection_points_wakeup('inplace-before-pin'); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# XXX extant bug |
||||||
|
permutation |
||||||
|
vac1(mkrels3) # reads pg_class tuple T0 for vactest.orig50, xmax invalid |
||||||
|
grant2 # T0 becomes eligible for pruning, T1 is successor |
||||||
|
vac3 # T0 becomes LP_UNUSED |
||||||
|
mkrels3 # T0 reused; vac1 wakes and overwrites the reused T0 |
||||||
|
read1 |
Loading…
Reference in new issue