Skip to content

Commit 9344ecd

Browse files
nkeyCommitfest Bot
nkey
authored and
Commitfest Bot
committed
Add isolation test to reproduce dirty snapshot scan issue
This commit introduces an isolation test to reliably reproduce the issue where non-MVCC index scans using SnapshotDirty can miss tuples due to concurrent modifications. This situation can lead to incorrect results. To facilitate this test, new injection point added in the index_getnext_slot. Changes include: * Added injection point in src/backend/access/index/indexam.c * Updated Makefile and meson.build to include the new dirty_index_scan isolation test. * Created a new isolation spec dirty_index_scan.spec and its expected output to define and verify the test steps. * This test complements the previous fix by demonstrating the issue and verifying that the fix effectively addresses it.
1 parent 0064020 commit 9344ecd

File tree

6 files changed

+77
-1
lines changed

6 files changed

+77
-1
lines changed

src/backend/access/index/indexam.c

+8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "utils/ruleutils.h"
5858
#include "utils/snapmgr.h"
5959
#include "utils/syscache.h"
60+
#include "utils/injection_point.h"
6061

6162

6263
/* ----------------------------------------------------------------
@@ -741,6 +742,13 @@ index_getnext_slot(IndexScanDesc scan, ScanDirection direction, TupleTableSlot *
741742
* the index.
742743
*/
743744
Assert(ItemPointerIsValid(&scan->xs_heaptid));
745+
#ifdef USE_INJECTION_POINTS
746+
if (!IsCatalogRelationOid(scan->indexRelation->rd_id))
747+
{
748+
INJECTION_POINT("index_getnext_slot_before_fetch");
749+
}
750+
#endif
751+
744752
if (index_fetch_heap(scan, slot))
745753
return true;
746754
}

src/backend/executor/execIndexing.c

+3
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
#include "utils/multirangetypes.h"
118118
#include "utils/rangetypes.h"
119119
#include "utils/snapmgr.h"
120+
#include "utils/injection_point.h"
120121

121122
/* waitMode argument to check_exclusion_or_unique_constraint() */
122123
typedef enum
@@ -943,6 +944,8 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
943944

944945
ExecDropSingleTupleTableSlot(existing_slot);
945946

947+
if (!conflict)
948+
INJECTION_POINT("check_exclusion_or_unique_constraint_no_conflict");
946949
return !conflict;
947950
}
948951

src/test/modules/injection_points/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ PGFILEDESC = "injection_points - facility for injection points"
1414
REGRESS = injection_points hashagg reindex_conc
1515
REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
1616

17-
ISOLATION = basic inplace syscache-update-pruned
17+
ISOLATION = basic inplace syscache-update-pruned dirty_index_scan
1818

1919
TAP_TESTS = 1
2020

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Parsed test spec with 3 sessions
2+
3+
starting permutation: s1_s1 s2_s1 s3_s1
4+
injection_points_attach
5+
-----------------------
6+
7+
(1 row)
8+
9+
step s1_s1: INSERT INTO test.tbl VALUES(42, 1) on conflict(i) do update set n = EXCLUDED.n + 1; <waiting ...>
10+
step s2_s1: UPDATE test.tbl SET n = n + 1 WHERE i = 42; <waiting ...>
11+
step s3_s1:
12+
SELECT injection_points_detach('index_getnext_slot_before_fetch');
13+
SELECT injection_points_wakeup('index_getnext_slot_before_fetch');
14+
<waiting ...>
15+
step s1_s1: <... completed>
16+
step s2_s1: <... completed>
17+
step s3_s1: <... completed>
18+
injection_points_detach
19+
-----------------------
20+
21+
(1 row)
22+
23+
injection_points_wakeup
24+
-----------------------
25+
26+
(1 row)
27+

src/test/modules/injection_points/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ tests += {
4747
'basic',
4848
'inplace',
4949
'syscache-update-pruned',
50+
'dirty_index_scan',
5051
],
5152
'runningcheck': false, # see syscache-update-pruned
5253
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
setup
2+
{
3+
CREATE EXTENSION injection_points;
4+
CREATE SCHEMA test;
5+
CREATE UNLOGGED TABLE test.tbl(i int primary key, n int);
6+
CREATE INDEX tbl_n_idx ON test.tbl(n);
7+
INSERT INTO test.tbl VALUES(42,1);
8+
}
9+
10+
teardown
11+
{
12+
DROP SCHEMA test CASCADE;
13+
DROP EXTENSION injection_points;
14+
}
15+
16+
session s1
17+
setup {
18+
SELECT injection_points_set_local();
19+
SELECT injection_points_attach('check_exclusion_or_unique_constraint_no_conflict', 'error');
20+
SELECT injection_points_attach('index_getnext_slot_before_fetch', 'wait');
21+
}
22+
23+
step s1_s1 { INSERT INTO test.tbl VALUES(42, 1) on conflict(i) do update set n = EXCLUDED.n + 1; }
24+
25+
session s2
26+
step s2_s1 { UPDATE test.tbl SET n = n + 1 WHERE i = 42; }
27+
28+
session s3
29+
step s3_s1 {
30+
SELECT injection_points_detach('index_getnext_slot_before_fetch');
31+
SELECT injection_points_wakeup('index_getnext_slot_before_fetch');
32+
}
33+
34+
permutation
35+
s1_s1
36+
s2_s1(*)
37+
s3_s1(s1_s1)

0 commit comments

Comments
 (0)