Hell, everyone!

Using the brand-new injection points support in specs, I created a spec to
reproduce the issue.

It fails like this currently:

make -C src/test/modules/injection_points/ check

@@ -64,6 +64,7 @@

 step s3_s1: <... completed>
 step s2_s1: <... completed>
+ERROR:  duplicate key value violates unique constraint "tbl_pkey_ccold"

 starting permutation: s3_s1 s2_s1 s4_s1 s1_s1 s4_s2 s4_s3
 injection_points_attach
@@ -129,3 +130,4 @@

 step s3_s1: <... completed>
 step s2_s1: <... completed>
+ERROR:  duplicate key value violates unique constraint "tbl_pkey"

Best regards,
Mikhail.
From 6731d0a2df03eb291b05b5424f8e4aa63c2216ee Mon Sep 17 00:00:00 2001
From: nkey <n...@toloka.ai>
Date: Sun, 4 Aug 2024 21:24:49 +0200
Subject: [PATCH v1] spec to reproduce issue with REINDEX CONCURRENTLY and
 INSERT ON CONFLICT UPDATE

---
 src/backend/commands/indexcmds.c              |   3 +-
 src/backend/executor/execIndexing.c           |   3 +
 src/backend/executor/nodeModifyTable.c        |   3 +
 src/test/modules/injection_points/Makefile    |   2 +-
 .../expected/reindex_concurrently_upsert.out  | 131 ++++++++++++++++++
 src/test/modules/injection_points/meson.build |   1 +
 .../specs/reindex_concurrently_upsert.spec    |  70 ++++++++++
 7 files changed, 211 insertions(+), 2 deletions(-)
 create mode 100644 src/test/modules/injection_points/expected/reindex_concurrently_upsert.out
 create mode 100644 src/test/modules/injection_points/specs/reindex_concurrently_upsert.spec

diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 2caab88aa5..9437654b11 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,6 +69,7 @@
 #include "utils/regproc.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/injection_point.h"
 
 
 /* non-export function prototypes */
@@ -4078,7 +4079,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 	 * the same time to make sure we only get constraint violations from the
 	 * indexes with the correct names.
 	 */
-
+	INJECTION_POINT("reindex_relation_concurrently_before_swap");
 	StartTransactionCommand();
 
 	/*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 9f05b3654c..1d451a329a 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -115,6 +115,7 @@
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
 #include "utils/snapmgr.h"
+#include "utils/injection_point.h"
 
 /* waitMode argument to check_exclusion_or_unique_constraint() */
 typedef enum
@@ -901,6 +902,8 @@ retry:
 	econtext->ecxt_scantuple = save_scantuple;
 
 	ExecDropSingleTupleTableSlot(existing_slot);
+	if (!conflict)
+		INJECTION_POINT("check_exclusion_or_unique_constraint_no_conflict");
 
 	return !conflict;
 }
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 4913e49319..d91b0a1cc7 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -69,6 +69,7 @@
 #include "utils/datum.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/injection_point.h"
 
 
 typedef struct MTTargetRelLookup
@@ -1085,6 +1086,8 @@ ExecInsert(ModifyTableContext *context,
 				}
 			}
 
+			INJECTION_POINT("exec_insert_before_insert_speculative");
+
 			/*
 			 * Before we start insertion proper, acquire our "speculative
 			 * insertion lock".  Others can use that to wait for us to decide
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index 2ffd2f77ed..f8a2a7630d 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -9,7 +9,7 @@ PGFILEDESC = "injection_points - facility for injection points"
 REGRESS = injection_points
 REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
 
-ISOLATION = inplace
+ISOLATION = inplace reindex_concurrently_upsert
 
 # The injection points are cluster-wide, so disable installcheck
 NO_INSTALLCHECK = 1
diff --git a/src/test/modules/injection_points/expected/reindex_concurrently_upsert.out b/src/test/modules/injection_points/expected/reindex_concurrently_upsert.out
new file mode 100644
index 0000000000..3e855e2223
--- /dev/null
+++ b/src/test/modules/injection_points/expected/reindex_concurrently_upsert.out
@@ -0,0 +1,131 @@
+Parsed test spec with 4 sessions
+
+starting permutation: s3_s1 s1_s1 s4_s1 s2_s1 s4_s2 s4_s3
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s3_s1: REINDEX INDEX CONCURRENTLY test.tbl_pkey; <waiting ...>
+step s1_s1: INSERT INTO test.tbl VALUES(13,now()) on conflict(i) do update set updated_at = now(); <waiting ...>
+step s4_s1: 
+	SELECT injection_points_wakeup('reindex_relation_concurrently_before_swap');
+	SELECT injection_points_detach('reindex_relation_concurrently_before_swap');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s2_s1: INSERT INTO test.tbl VALUES(13,now()) on conflict(i) do update set updated_at = now(); <waiting ...>
+step s4_s2: 
+	SELECT injection_points_wakeup('check_exclusion_or_unique_constraint_no_conflict');
+	SELECT injection_points_detach('check_exclusion_or_unique_constraint_no_conflict');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s1_s1: <... completed>
+step s4_s3: 
+	SELECT injection_points_wakeup('exec_insert_before_insert_speculative');
+	SELECT injection_points_detach('exec_insert_before_insert_speculative');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s3_s1: <... completed>
+step s2_s1: <... completed>
+
+starting permutation: s3_s1 s2_s1 s4_s1 s1_s1 s4_s2 s4_s3
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s3_s1: REINDEX INDEX CONCURRENTLY test.tbl_pkey; <waiting ...>
+step s2_s1: INSERT INTO test.tbl VALUES(13,now()) on conflict(i) do update set updated_at = now(); <waiting ...>
+step s4_s1: 
+	SELECT injection_points_wakeup('reindex_relation_concurrently_before_swap');
+	SELECT injection_points_detach('reindex_relation_concurrently_before_swap');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s1_s1: INSERT INTO test.tbl VALUES(13,now()) on conflict(i) do update set updated_at = now(); <waiting ...>
+step s4_s2: 
+	SELECT injection_points_wakeup('check_exclusion_or_unique_constraint_no_conflict');
+	SELECT injection_points_detach('check_exclusion_or_unique_constraint_no_conflict');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s1_s1: <... completed>
+step s4_s3: 
+	SELECT injection_points_wakeup('exec_insert_before_insert_speculative');
+	SELECT injection_points_detach('exec_insert_before_insert_speculative');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+step s3_s1: <... completed>
+step s2_s1: <... completed>
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 3c23c14d81..7caa9769b2 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -40,6 +40,7 @@ tests += {
   'isolation': {
     'specs': [
       'inplace',
+      'reindex_concurrently_upsert',
     ],
   },
 }
diff --git a/src/test/modules/injection_points/specs/reindex_concurrently_upsert.spec b/src/test/modules/injection_points/specs/reindex_concurrently_upsert.spec
new file mode 100644
index 0000000000..fcaee1a21c
--- /dev/null
+++ b/src/test/modules/injection_points/specs/reindex_concurrently_upsert.spec
@@ -0,0 +1,70 @@
+# Test race conditions involving:
+# - s1: UPSERT a tuple
+# - s2: UPSERT the same tuple
+# - s3: REINDEX concurrent primary key index
+# - s4: operations with injection points
+
+setup
+{
+	CREATE EXTENSION injection_points;
+	CREATE SCHEMA test;
+	CREATE UNLOGGED TABLE test.tbl(i int primary key, updated_at timestamp);
+	ALTER TABLE test.tbl SET (parallel_workers=0);
+}
+
+teardown
+{
+	DROP SCHEMA test CASCADE;
+	DROP EXTENSION injection_points;
+}
+
+session s1
+setup	{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('check_exclusion_or_unique_constraint_no_conflict', 'wait');
+}
+step s1_s1	{ INSERT INTO test.tbl VALUES(13,now()) on conflict(i) do update set updated_at = now(); }
+
+session s2
+setup	{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('exec_insert_before_insert_speculative', 'wait');
+}
+step s2_s1	{ INSERT INTO test.tbl VALUES(13,now()) on conflict(i) do update set updated_at = now(); }
+
+session s3
+setup	{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('reindex_relation_concurrently_before_swap', 'wait');
+}
+step s3_s1		{ REINDEX INDEX CONCURRENTLY test.tbl_pkey; }
+
+session s4
+step s4_s1		{
+	SELECT injection_points_wakeup('reindex_relation_concurrently_before_swap');
+	SELECT injection_points_detach('reindex_relation_concurrently_before_swap');
+}
+step s4_s2		{
+	SELECT injection_points_wakeup('check_exclusion_or_unique_constraint_no_conflict');
+	SELECT injection_points_detach('check_exclusion_or_unique_constraint_no_conflict');
+}
+step s4_s3		{
+	SELECT injection_points_wakeup('exec_insert_before_insert_speculative');
+	SELECT injection_points_detach('exec_insert_before_insert_speculative');
+}
+
+permutation
+	s3_s1
+	s1_s1
+	s4_s1
+	s2_s1
+	s4_s2
+	s4_s3
+
+permutation
+	s3_s1
+	s2_s1
+	s4_s1
+	s1_s1
+	s4_s2
+	s4_s3
\ No newline at end of file
-- 
2.34.1

Reply via email to