Skip to content

Commit c35f419

Browse files
committed
Add an injection_points isolation test suite.
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.com
1 parent abfbd13 commit c35f419

File tree

6 files changed

+154
-2
lines changed

6 files changed

+154
-2
lines changed

src/backend/access/heap/heapam.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include "storage/procarray.h"
6464
#include "storage/standby.h"
6565
#include "utils/datum.h"
66+
#include "utils/injection_point.h"
6667
#include "utils/inval.h"
6768
#include "utils/relcache.h"
6869
#include "utils/snapmgr.h"
@@ -6080,6 +6081,7 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
60806081
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
60816082
errmsg("cannot update tuples during a parallel operation")));
60826083

6084+
INJECTION_POINT("inplace-before-pin");
60836085
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
60846086
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
60856087
page = (Page) BufferGetPage(buffer);

src/backend/utils/adt/waitfuncs.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@
1414

1515
#include "catalog/pg_type.h"
1616
#include "storage/predicate_internals.h"
17+
#include "storage/proc.h"
18+
#include "storage/procarray.h"
1719
#include "utils/array.h"
1820
#include "utils/builtins.h"
21+
#include "utils/wait_event.h"
22+
23+
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
1924

2025

2126
/*
2227
* pg_isolation_test_session_is_blocked - support function for isolationtester
2328
*
2429
* Check if specified PID is blocked by any of the PIDs listed in the second
2530
* argument. Currently, this looks for blocking caused by waiting for
26-
* heavyweight locks or safe snapshots. We ignore blockage caused by PIDs
27-
* not directly under the isolationtester's control, eg autovacuum.
31+
* injection points, heavyweight locks, or safe snapshots. We ignore blockage
32+
* caused by PIDs not directly under the isolationtester's control, eg
33+
* autovacuum.
2834
*
2935
* This is an undocumented function intended for use by the isolation tester,
3036
* and may change in future releases as required for testing purposes.
@@ -34,6 +40,8 @@ pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS)
3440
{
3541
int blocked_pid = PG_GETARG_INT32(0);
3642
ArrayType *interesting_pids_a = PG_GETARG_ARRAYTYPE_P(1);
43+
PGPROC *proc;
44+
const char *wait_event_type;
3745
ArrayType *blocking_pids_a;
3846
int32 *interesting_pids;
3947
int32 *blocking_pids;
@@ -43,6 +51,15 @@ pg_isolation_test_session_is_blocked(PG_FUNCTION_ARGS)
4351
int i,
4452
j;
4553

54+
/* Check if blocked_pid is in an injection point. */
55+
proc = BackendPidGetProc(blocked_pid);
56+
if (proc == NULL)
57+
PG_RETURN_BOOL(false); /* session gone: definitely unblocked */
58+
wait_event_type =
59+
pgstat_get_wait_event_type(UINT32_ACCESS_ONCE(proc->wait_event_info));
60+
if (wait_event_type && strcmp("InjectionPoint", wait_event_type) == 0)
61+
PG_RETURN_BOOL(true);
62+
4663
/* Validate the passed-in array */
4764
Assert(ARR_ELEMTYPE(interesting_pids_a) == INT4OID);
4865
if (array_contains_nulls(interesting_pids_a))

src/test/modules/injection_points/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ PGFILEDESC = "injection_points - facility for injection points"
99
REGRESS = injection_points
1010
REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
1111

12+
ISOLATION = inplace
13+
1214
# The injection points are cluster-wide, so disable installcheck
1315
NO_INSTALLCHECK = 1
1416

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Parsed test spec with 3 sessions
2+
3+
starting permutation: vac1 grant2 vac3 mkrels3 read1
4+
mkrels
5+
------
6+
7+
(1 row)
8+
9+
injection_points_attach
10+
-----------------------
11+
12+
(1 row)
13+
14+
step vac1: VACUUM vactest.orig50; -- wait during inplace update <waiting ...>
15+
step grant2: GRANT SELECT ON TABLE vactest.orig50 TO PUBLIC;
16+
step vac3: VACUUM pg_class;
17+
step mkrels3:
18+
SELECT vactest.mkrels('intruder', 1, 100); -- repopulate LP_UNUSED
19+
SELECT injection_points_detach('inplace-before-pin');
20+
SELECT injection_points_wakeup('inplace-before-pin');
21+
22+
mkrels
23+
------
24+
25+
(1 row)
26+
27+
injection_points_detach
28+
-----------------------
29+
30+
(1 row)
31+
32+
injection_points_wakeup
33+
-----------------------
34+
35+
(1 row)
36+
37+
step vac1: <... completed>
38+
step read1:
39+
REINDEX TABLE pg_class; -- look for duplicates
40+
SELECT reltuples = -1 AS reltuples_unknown
41+
FROM pg_class WHERE oid = 'vactest.orig50'::regclass;
42+
43+
ERROR: could not create unique index "pg_class_oid_index"

src/test/modules/injection_points/meson.build

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,9 @@ tests += {
3737
# The injection points are cluster-wide, so disable installcheck
3838
'runningcheck': false,
3939
},
40+
'isolation': {
41+
'specs': [
42+
'inplace',
43+
],
44+
},
4045
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy