aboutsummaryrefslogtreecommitdiff
path: root/src/test/modules/injection_points/specs/inplace.spec
blob: 86539a5bd2f119fa413194e4b0bb85e5cb976c0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# 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);
}
teardown
{
	DROP SCHEMA vactest CASCADE;
	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; }
step revoke2	{ REVOKE SELECT ON TABLE vactest.orig50 FROM PUBLIC; }
step begin2		{ BEGIN; }
step c2			{ COMMIT; }
step r2			{ ROLLBACK; }

# 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');
}


# target gains a successor at the last moment
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			# vac1 wakes, scans to T1
	read1

# target already has a successor, which commits
permutation
	begin2
	grant2			# T0.t_ctid = T1
	vac1(mkrels3)	# reads T0 for vactest.orig50
	c2				# T0 becomes eligible for pruning
	vac3			# T0 becomes LP_UNUSED
	mkrels3			# vac1 wakes, scans to T1
	read1

# target already has a successor, which becomes LP_UNUSED at the last moment
permutation
	begin2
	grant2			# T0.t_ctid = T1
	vac1(mkrels3)	# reads T0 for vactest.orig50
	r2				# T1 becomes eligible for pruning
	vac3			# T1 becomes LP_UNUSED
	mkrels3			# reuse T1; vac1 scans to T0
	read1

# target already has a successor, which becomes LP_REDIRECT at the last moment
permutation
	begin2
	grant2			# T0.t_ctid = T1, non-HOT due to filled page
	vac1(mkrels3)	# reads T0
	c2
	revoke2			# HOT update to T2
	grant2			# HOT update to T3
	vac3			# T1 becomes LP_REDIRECT
	mkrels3			# reuse T2; vac1 scans to T3
	read1

# waiting for updater to end
permutation
	vac1(c2)		# reads pg_class tuple T0 for vactest.orig50, xmax invalid
	begin2
	grant2			# T0.t_ctid = T1, non-HOT due to filled page
	revoke2			# HOT update to T2
	mkrels3			# vac1 awakes briefly, then waits for s2
	c2
	read1

# Another LP_UNUSED.  This time, do change the live tuple.  Final live tuple
# body is identical to original, at a different TID.
permutation
	begin2
	grant2			# T0.t_ctid = T1, non-HOT due to filled page
	vac1(mkrels3)	# reads T0
	r2				# T1 becomes eligible for pruning
	grant2			# T0.t_ctid = T2; T0 becomes eligible for pruning
	revoke2			# T2.t_ctid = T3; T2 becomes eligible for pruning
	vac3			# T0, T1 & T2 become LP_UNUSED
	mkrels3			# reuse T0, T1 & T2; vac1 scans to T3
	read1

# Another LP_REDIRECT.  Compared to the earlier test, omit the last grant2.
# Hence, final live tuple body is identical to original, at a different TID.
permutation begin2 grant2 vac1(mkrels3) c2 revoke2 vac3 mkrels3 read1