mirror of https://github.com/postgres/postgres
This covers both regular and inplace changes, since bugs arise at their intersection. Where marked, these witness extant bugs. Back-patch to v12 (all supported versions). Reviewed (in an earlier version) by Robert Haas. Discussion: https://postgr.es/m/20240512232923.aa.nmisch@google.compull/165/head
parent
22a4b104ba
commit
0844b39689
@ -0,0 +1,32 @@ |
||||
Parsed test spec with 3 sessions |
||||
|
||||
starting permutation: cachefill3 cir1 cic2 ddl3 read1 |
||||
step cachefill3: TABLE newly_indexed; |
||||
c |
||||
- |
||||
(0 rows) |
||||
|
||||
step cir1: BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK; |
||||
step cic2: CREATE INDEX i2 ON newly_indexed (c); |
||||
step ddl3: ALTER TABLE newly_indexed ADD extra int; |
||||
step read1: |
||||
SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
|
||||
starting permutation: cir1 cic2 ddl3 read1 |
||||
step cir1: BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK; |
||||
step cic2: CREATE INDEX i2 ON newly_indexed (c); |
||||
step ddl3: ALTER TABLE newly_indexed ADD extra int; |
||||
step read1: |
||||
SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
t |
||||
(1 row) |
||||
|
@ -0,0 +1,28 @@ |
||||
Parsed test spec with 3 sessions |
||||
|
||||
starting permutation: snap3 b1 grant1 vac2 snap3 c1 cmp3 |
||||
step snap3: |
||||
INSERT INTO frozen_witness |
||||
SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog; |
||||
|
||||
step b1: BEGIN; |
||||
step grant1: |
||||
GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee; |
||||
|
||||
step vac2: VACUUM (FREEZE); |
||||
step snap3: |
||||
INSERT INTO frozen_witness |
||||
SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog; |
||||
|
||||
step c1: COMMIT; |
||||
step cmp3: |
||||
SELECT 'datfrozenxid retreated' |
||||
FROM pg_database |
||||
WHERE datname = current_catalog |
||||
AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness); |
||||
|
||||
?column? |
||||
---------------------- |
||||
datfrozenxid retreated |
||||
(1 row) |
||||
|
@ -0,0 +1,225 @@ |
||||
Parsed test spec with 5 sessions |
||||
|
||||
starting permutation: b1 grant1 read2 addk2 c1 read2 |
||||
step b1: BEGIN; |
||||
step grant1: |
||||
GRANT SELECT ON intra_grant_inplace TO PUBLIC; |
||||
|
||||
step read2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
step c1: COMMIT; |
||||
step read2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
|
||||
starting permutation: keyshr5 addk2 |
||||
step keyshr5: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
|
||||
starting permutation: keyshr5 b3 sfnku3 addk2 r3 |
||||
step keyshr5: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step b3: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step sfnku3: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
step r3: ROLLBACK; |
||||
|
||||
starting permutation: b2 sfnku2 addk2 c2 |
||||
step b2: BEGIN; |
||||
step sfnku2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
step c2: COMMIT; |
||||
|
||||
starting permutation: keyshr5 b2 sfnku2 addk2 c2 |
||||
step keyshr5: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step b2: BEGIN; |
||||
step sfnku2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
step c2: COMMIT; |
||||
|
||||
starting permutation: b3 sfu3 b1 grant1 read2 addk2 r3 c1 read2 |
||||
step b3: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step sfu3: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step b1: BEGIN; |
||||
step grant1: |
||||
GRANT SELECT ON intra_grant_inplace TO PUBLIC; |
||||
<waiting ...> |
||||
step read2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
step r3: ROLLBACK; |
||||
step grant1: <... completed> |
||||
step c1: COMMIT; |
||||
step read2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
|
||||
starting permutation: b2 sfnku2 b1 grant1 addk2 c2 c1 read2 |
||||
step b2: BEGIN; |
||||
step sfnku2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
step b1: BEGIN; |
||||
step grant1: |
||||
GRANT SELECT ON intra_grant_inplace TO PUBLIC; |
||||
<waiting ...> |
||||
step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); |
||||
step c2: COMMIT; |
||||
step grant1: <... completed> |
||||
step c1: COMMIT; |
||||
step read2: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass; |
||||
|
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
|
||||
starting permutation: b1 grant1 b3 sfu3 revoke4 c1 r3 |
||||
step b1: BEGIN; |
||||
step grant1: |
||||
GRANT SELECT ON intra_grant_inplace TO PUBLIC; |
||||
|
||||
step b3: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step sfu3: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE; |
||||
<waiting ...> |
||||
step revoke4: |
||||
DO $$ |
||||
BEGIN |
||||
REVOKE SELECT ON intra_grant_inplace FROM PUBLIC; |
||||
EXCEPTION WHEN others THEN |
||||
RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 'REDACTED'); |
||||
END |
||||
$$; |
||||
<waiting ...> |
||||
step c1: COMMIT; |
||||
step sfu3: <... completed> |
||||
relhasindex |
||||
----------- |
||||
f |
||||
(1 row) |
||||
|
||||
s4: WARNING: got: tuple concurrently updated |
||||
step revoke4: <... completed> |
||||
step r3: ROLLBACK; |
||||
|
||||
starting permutation: b1 drop1 b3 sfu3 revoke4 c1 r3 |
||||
step b1: BEGIN; |
||||
step drop1: |
||||
DROP TABLE intra_grant_inplace; |
||||
|
||||
step b3: BEGIN ISOLATION LEVEL READ COMMITTED; |
||||
step sfu3: |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE; |
||||
<waiting ...> |
||||
step revoke4: |
||||
DO $$ |
||||
BEGIN |
||||
REVOKE SELECT ON intra_grant_inplace FROM PUBLIC; |
||||
EXCEPTION WHEN others THEN |
||||
RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 'REDACTED'); |
||||
END |
||||
$$; |
||||
<waiting ...> |
||||
step c1: COMMIT; |
||||
step sfu3: <... completed> |
||||
relhasindex |
||||
----------- |
||||
(0 rows) |
||||
|
||||
s4: WARNING: got: tuple concurrently deleted |
||||
step revoke4: <... completed> |
||||
step r3: ROLLBACK; |
@ -0,0 +1,38 @@ |
||||
# If a heap_update() caller retrieves its oldtup from a cache, it's possible |
||||
# for that cache entry to predate an inplace update, causing loss of that |
||||
# inplace update. This arises because the transaction may abort before |
||||
# sending the inplace invalidation message to the shared queue. |
||||
|
||||
setup |
||||
{ |
||||
CREATE TABLE newly_indexed (c int); |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
DROP TABLE newly_indexed; |
||||
} |
||||
|
||||
session s1 |
||||
step cir1 { BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK; } |
||||
step read1 { |
||||
SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass; |
||||
} |
||||
|
||||
session s2 |
||||
step cic2 { CREATE INDEX i2 ON newly_indexed (c); } |
||||
|
||||
session s3 |
||||
step cachefill3 { TABLE newly_indexed; } |
||||
step ddl3 { ALTER TABLE newly_indexed ADD extra int; } |
||||
|
||||
|
||||
permutation |
||||
cachefill3 # populates the pg_class row in the catcache |
||||
cir1 # sets relhasindex=true; rollback discards cache inval |
||||
cic2 # sees relhasindex=true, skips changing it (so no inval) |
||||
ddl3 # cached row as the oldtup of an update, losing relhasindex |
||||
read1 # observe damage XXX is an extant bug |
||||
|
||||
# without cachefill3, no bug |
||||
permutation cir1 cic2 ddl3 read1 |
@ -0,0 +1,46 @@ |
||||
# GRANT's lock is the catalog tuple xmax. GRANT doesn't acquire a heavyweight |
||||
# lock on the object undergoing an ACL change. In-place updates, namely |
||||
# datfrozenxid, need special code to cope. |
||||
|
||||
setup |
||||
{ |
||||
CREATE ROLE regress_temp_grantee; |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
REVOKE ALL ON DATABASE isolation_regression FROM regress_temp_grantee; |
||||
DROP ROLE regress_temp_grantee; |
||||
} |
||||
|
||||
# heap_update(pg_database) |
||||
session s1 |
||||
step b1 { BEGIN; } |
||||
step grant1 { |
||||
GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee; |
||||
} |
||||
step c1 { COMMIT; } |
||||
|
||||
# inplace update |
||||
session s2 |
||||
step vac2 { VACUUM (FREEZE); } |
||||
|
||||
# observe datfrozenxid |
||||
session s3 |
||||
setup { |
||||
CREATE TEMP TABLE frozen_witness (x xid); |
||||
} |
||||
step snap3 { |
||||
INSERT INTO frozen_witness |
||||
SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog; |
||||
} |
||||
step cmp3 { |
||||
SELECT 'datfrozenxid retreated' |
||||
FROM pg_database |
||||
WHERE datname = current_catalog |
||||
AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness); |
||||
} |
||||
|
||||
|
||||
# XXX extant bug |
||||
permutation snap3 b1 grant1 vac2(c1) snap3 c1 cmp3 |
@ -0,0 +1,153 @@ |
||||
# GRANT's lock is the catalog tuple xmax. GRANT doesn't acquire a heavyweight |
||||
# lock on the object undergoing an ACL change. Inplace updates, such as |
||||
# relhasindex=true, need special code to cope. |
||||
|
||||
setup |
||||
{ |
||||
CREATE TABLE intra_grant_inplace (c int); |
||||
} |
||||
|
||||
teardown |
||||
{ |
||||
DROP TABLE IF EXISTS intra_grant_inplace; |
||||
} |
||||
|
||||
# heap_update() |
||||
session s1 |
||||
step b1 { BEGIN; } |
||||
step grant1 { |
||||
GRANT SELECT ON intra_grant_inplace TO PUBLIC; |
||||
} |
||||
step drop1 { |
||||
DROP TABLE intra_grant_inplace; |
||||
} |
||||
step c1 { COMMIT; } |
||||
|
||||
# inplace update |
||||
session s2 |
||||
step read2 { |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass; |
||||
} |
||||
step b2 { BEGIN; } |
||||
step addk2 { ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); } |
||||
step sfnku2 { |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE; |
||||
} |
||||
step c2 { COMMIT; } |
||||
|
||||
# rowmarks |
||||
session s3 |
||||
step b3 { BEGIN ISOLATION LEVEL READ COMMITTED; } |
||||
step sfnku3 { |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE; |
||||
} |
||||
step sfu3 { |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE; |
||||
} |
||||
step r3 { ROLLBACK; } |
||||
|
||||
# Additional heap_update() |
||||
session s4 |
||||
# swallow error message to keep any OID value out of expected output |
||||
step revoke4 { |
||||
DO $$ |
||||
BEGIN |
||||
REVOKE SELECT ON intra_grant_inplace FROM PUBLIC; |
||||
EXCEPTION WHEN others THEN |
||||
RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 'REDACTED'); |
||||
END |
||||
$$; |
||||
} |
||||
|
||||
# Additional rowmarks |
||||
session s5 |
||||
setup { BEGIN; } |
||||
step keyshr5 { |
||||
SELECT relhasindex FROM pg_class |
||||
WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE; |
||||
} |
||||
teardown { ROLLBACK; } |
||||
|
||||
|
||||
# XXX extant bugs: permutation comments refer to planned post-bugfix behavior |
||||
|
||||
permutation |
||||
b1 |
||||
grant1 |
||||
read2 |
||||
addk2(c1) # inplace waits |
||||
c1 |
||||
read2 |
||||
|
||||
# inplace thru KEY SHARE |
||||
permutation |
||||
keyshr5 |
||||
addk2 |
||||
|
||||
# inplace wait NO KEY UPDATE w/ KEY SHARE |
||||
permutation |
||||
keyshr5 |
||||
b3 |
||||
sfnku3 |
||||
addk2(r3) |
||||
r3 |
||||
|
||||
# same-xact rowmark |
||||
permutation |
||||
b2 |
||||
sfnku2 |
||||
addk2 |
||||
c2 |
||||
|
||||
# same-xact rowmark in multixact |
||||
permutation |
||||
keyshr5 |
||||
b2 |
||||
sfnku2 |
||||
addk2 |
||||
c2 |
||||
|
||||
permutation |
||||
b3 |
||||
sfu3 |
||||
b1 |
||||
grant1(r3) # acquire LockTuple(), await sfu3 xmax |
||||
read2 |
||||
addk2(c1) # block in LockTuple() behind grant1 |
||||
r3 # unblock grant1; addk2 now awaits grant1 xmax |
||||
c1 |
||||
read2 |
||||
|
||||
permutation |
||||
b2 |
||||
sfnku2 |
||||
b1 |
||||
grant1(c2) # acquire LockTuple(), await sfnku2 xmax |
||||
addk2 # block in LockTuple() behind grant1 = deadlock |
||||
c2 |
||||
c1 |
||||
read2 |
||||
|
||||
# SearchSysCacheLocked1() calling LockRelease() |
||||
permutation |
||||
b1 |
||||
grant1 |
||||
b3 |
||||
sfu3(c1) # acquire LockTuple(), await grant1 xmax |
||||
revoke4(sfu3) # block in LockTuple() behind sfu3 |
||||
c1 |
||||
r3 # revoke4 unlocks old tuple and finds new |
||||
|
||||
# SearchSysCacheLocked1() finding a tuple, then no tuple |
||||
permutation |
||||
b1 |
||||
drop1 |
||||
b3 |
||||
sfu3(c1) # acquire LockTuple(), await drop1 xmax |
||||
revoke4(sfu3) # block in LockTuple() behind sfu3 |
||||
c1 # sfu3 locks none; revoke4 unlocks old and finds none |
||||
r3 |
@ -0,0 +1,15 @@ |
||||
CREATE DATABASE regression_tbd |
||||
ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0; |
||||
ALTER DATABASE regression_tbd RENAME TO regression_utf8; |
||||
ALTER DATABASE regression_utf8 SET TABLESPACE regress_tblspace; |
||||
ALTER DATABASE regression_utf8 RESET TABLESPACE; |
||||
ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123; |
||||
-- Test PgDatabaseToastTable. Doing this with GRANT would be slow. |
||||
BEGIN; |
||||
UPDATE pg_database |
||||
SET datacl = array_fill(makeaclitem(10, 10, 'USAGE', false), ARRAY[5e5::int]) |
||||
WHERE datname = 'regression_utf8'; |
||||
-- load catcache entry, if nothing else does |
||||
ALTER DATABASE regression_utf8 RESET TABLESPACE; |
||||
ROLLBACK; |
||||
DROP DATABASE regression_utf8; |
@ -0,0 +1,17 @@ |
||||
CREATE DATABASE regression_tbd |
||||
ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0; |
||||
ALTER DATABASE regression_tbd RENAME TO regression_utf8; |
||||
ALTER DATABASE regression_utf8 SET TABLESPACE regress_tblspace; |
||||
ALTER DATABASE regression_utf8 RESET TABLESPACE; |
||||
ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123; |
||||
|
||||
-- Test PgDatabaseToastTable. Doing this with GRANT would be slow. |
||||
BEGIN; |
||||
UPDATE pg_database |
||||
SET datacl = array_fill(makeaclitem(10, 10, 'USAGE', false), ARRAY[5e5::int]) |
||||
WHERE datname = 'regression_utf8'; |
||||
-- load catcache entry, if nothing else does |
||||
ALTER DATABASE regression_utf8 RESET TABLESPACE; |
||||
ROLLBACK; |
||||
|
||||
DROP DATABASE regression_utf8; |
Loading…
Reference in new issue