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